summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/core/Android.bp2
-rw-r--r--apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java30
-rw-r--r--apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java37
-rw-r--r--apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java53
-rw-r--r--apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java21
-rw-r--r--apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java16
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java65
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java123
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java34
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java245
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java44
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java112
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java42
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java42
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java69
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java25
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java59
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java31
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java23
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java67
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java28
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java38
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java18
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java38
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java18
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java28
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java43
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java28
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java23
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java32
-rw-r--r--apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java3
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java6
-rw-r--r--core/api/current.txt20
-rw-r--r--core/api/module-lib-current.txt4
-rw-r--r--core/api/system-current.txt10
-rw-r--r--core/java/android/app/AppOpsManager.java23
-rw-r--r--core/java/android/app/BroadcastOptions.java74
-rw-r--r--core/java/android/app/SystemServiceRegistry.java9
-rw-r--r--core/java/android/app/TEST_MAPPING17
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.java131
-rw-r--r--core/java/android/content/ClipDescription.java13
-rw-r--r--core/java/android/content/ComponentName.java17
-rw-r--r--core/java/android/content/Context.java17
-rw-r--r--core/java/android/content/Intent.java15
-rw-r--r--core/java/android/content/pm/IBackgroundInstallControlService.aidl (renamed from core/jni/include_vm/android_runtime/vm.h)16
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java24
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java43
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java84
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java84
-rw-r--r--core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl1
-rw-r--r--core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl1
-rw-r--r--core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl1
-rw-r--r--core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java13
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java14
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java16
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl5
-rw-r--r--core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl (renamed from services/core/java/com/android/server/pm/CommitRequest.java)24
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl7
-rw-r--r--core/java/android/hardware/input/InputManager.java18
-rw-r--r--core/java/android/os/Binder.java42
-rw-r--r--core/java/android/os/BundleMerger.java379
-rw-r--r--core/java/android/os/Parcel.java11
-rw-r--r--core/java/android/os/PermissionEnforcer.java101
-rw-r--r--core/java/android/os/ServiceManagerNative.java4
-rw-r--r--core/java/android/provider/Settings.java27
-rw-r--r--core/java/android/service/dreams/DreamManagerInternal.java7
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java7
-rw-r--r--core/java/android/text/method/BaseKeyListener.java2
-rw-r--r--core/java/android/util/CharsetUtils.java17
-rwxr-xr-xcore/java/android/util/DisplayMetrics.java8
-rw-r--r--core/java/android/util/Xml.java2
-rw-r--r--core/java/android/view/IDisplayWindowInsetsController.aidl6
-rw-r--r--core/java/android/view/IWindowManager.aidl2
-rw-r--r--core/java/android/view/IWindowSession.aidl15
-rw-r--r--core/java/android/view/InputDevice.java17
-rw-r--r--core/java/android/view/InsetsController.java139
-rw-r--r--core/java/android/view/InsetsFrameProvider.java5
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java36
-rw-r--r--core/java/android/view/InsetsState.java7
-rw-r--r--core/java/android/view/MotionEvent.java2
-rw-r--r--core/java/android/view/PendingInsetsController.java14
-rw-r--r--core/java/android/view/VelocityTracker.java64
-rw-r--r--core/java/android/view/ViewRootImpl.java74
-rw-r--r--core/java/android/view/ViewRootInsetsControllerHost.java10
-rw-r--r--core/java/android/view/WindowInsets.java23
-rw-r--r--core/java/android/view/WindowInsetsController.java6
-rw-r--r--core/java/android/view/WindowLayout.java7
-rw-r--r--core/java/android/view/WindowlessWindowLayout.java3
-rw-r--r--core/java/android/view/WindowlessWindowManager.java10
-rw-r--r--core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java (renamed from core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java)38
-rw-r--r--core/java/android/view/inputmethod/InputMethodManagerGlobal.java104
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java73
-rw-r--r--core/java/android/window/StartingWindowInfo.java13
-rw-r--r--core/java/com/android/internal/inputmethod/ImeTracing.java9
-rw-r--r--core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java3
-rw-r--r--core/java/com/android/internal/policy/DecorView.java44
-rw-r--r--core/java/com/android/internal/util/ArtBinaryXmlPullParser.java3
-rw-r--r--core/java/com/android/internal/util/ArtBinaryXmlSerializer.java3
-rw-r--r--core/java/com/android/internal/util/ArtFastDataInput.java2
-rw-r--r--core/java/com/android/internal/util/ArtFastDataOutput.java2
-rw-r--r--core/java/com/android/internal/util/BinaryXmlPullParser.java945
-rw-r--r--core/java/com/android/internal/util/BinaryXmlSerializer.java404
-rw-r--r--core/java/com/android/internal/util/FastDataInput.java300
-rw-r--r--core/java/com/android/internal/util/FastDataOutput.java269
-rw-r--r--core/java/com/android/internal/util/ModifiedUtf8.java110
-rw-r--r--core/java/com/android/modules/utils/TypedXmlPullParser.java328
-rw-r--r--core/java/com/android/modules/utils/TypedXmlSerializer.java101
-rw-r--r--core/jni/Android.bp22
-rw-r--r--core/jni/AndroidRuntime.cpp1
-rw-r--r--core/jni/android_os_Parcel.cpp7
-rw-r--r--core/jni/android_view_InputDevice.cpp2
-rw-r--r--core/jni/android_view_VelocityTracker.cpp7
-rw-r--r--core/proto/android/server/activitymanagerservice.proto1
-rw-r--r--core/res/AndroidManifest.xml9
-rw-r--r--core/res/res/color/letterbox_background.xml19
-rw-r--r--core/res/res/values-af/strings.xml1
-rw-r--r--core/res/res/values-am/strings.xml1
-rw-r--r--core/res/res/values-ar/strings.xml1
-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.xml1
-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.xml1
-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.xml5
-rw-r--r--core/res/res/values-es/strings.xml1
-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.xml1
-rw-r--r--core/res/res/values-gu/strings.xml1
-rw-r--r--core/res/res/values-hi/strings.xml3
-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.xml1
-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.xml3
-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.xml1
-rw-r--r--core/res/res/values-or/strings.xml1
-rw-r--r--core/res/res/values-pa/strings.xml1
-rw-r--r--core/res/res/values-pl/strings.xml1
-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.xml1
-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.xml1
-rw-r--r--core/res/res/values-ta/strings.xml1
-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.xml1
-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/config.xml2
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java433
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java35
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java171
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java221
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java51
-rw-r--r--core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java277
-rw-r--r--core/tests/coretests/OWNERS3
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java277
-rw-r--r--core/tests/coretests/src/android/app/backup/OWNERS1
-rw-r--r--core/tests/coretests/src/android/os/BundleMergerTest.java408
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java7
-rw-r--r--core/tests/coretests/src/android/text/method/BackspaceTest.java6
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java75
-rw-r--r--core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java61
-rw-r--r--core/tests/coretests/src/com/android/internal/util/FastDataTest.java3
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java2
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java157
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java142
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java7
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java5
-rw-r--r--location/java/android/location/GnssCapabilities.java6
-rw-r--r--location/java/android/location/GnssStatus.java6
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java2
-rw-r--r--packages/CompanionDeviceManager/res/values/styles.xml1
-rw-r--r--packages/CredentialManager/res/values/strings.xml3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt44
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt53
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle4
-rw-r--r--packages/SettingsLib/Spa/spa/res/values-night/themes.xml20
-rw-r--r--packages/SettingsLib/Spa/spa/res/values/themes.xml8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt54
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt48
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt16
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt80
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt32
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt59
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt29
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt61
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt4
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt (renamed from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt)22
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt84
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt8
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java32
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java126
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java12
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java76
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java18
-rw-r--r--packages/SystemUI/AndroidManifest.xml6
-rw-r--r--packages/SystemUI/docs/device-entry/quickaffordance.md2
-rw-r--r--packages/SystemUI/res-keyguard/values-af/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-am/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ar/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-as/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-az/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-be/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-bg/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-bn/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-bs/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ca/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-cs/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-da/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-de/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-el/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rAU/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rCA/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rGB/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rIN/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rXC/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-es-rUS/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-es/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-et/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-eu/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-fa/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-fi/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-fr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-gl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-gu/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hi/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hu/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-hy/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-in/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-is/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-it/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-iw/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ja/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ka/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-kk/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-km/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-kn/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ko/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ky/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-lo/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-lt/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-lv/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-mk/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ml/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-mn/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-mr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ms/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-my/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-nb/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ne/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-nl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-or/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-pa/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-pl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-pt/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-ro/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ru/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-si/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sk/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sl/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sq/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sv/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-sw/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ta/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-te/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-th/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-tl/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-tr/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-uk/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-ur/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-uz/strings.xml7
-rw-r--r--packages/SystemUI/res-keyguard/values-vi/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml11
-rw-r--r--packages/SystemUI/res-keyguard/values-zu/strings.xml11
-rw-r--r--packages/SystemUI/res/drawable/accessibility_floating_message_background.xml22
-rw-r--r--packages/SystemUI/res/drawable/overlay_badge_background.xml21
-rw-r--r--packages/SystemUI/res/layout-land/auth_credential_password_view.xml4
-rw-r--r--packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml4
-rw-r--r--packages/SystemUI/res/layout/auth_credential_password_view.xml4
-rw-r--r--packages/SystemUI/res/layout/auth_credential_pattern_view.xml4
-rw-r--r--packages/SystemUI/res/layout/internet_connectivity_dialog.xml5
-rw-r--r--packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml63
-rw-r--r--packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml3
-rw-r--r--packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml1
-rw-r--r--packages/SystemUI/res/layout/screenshot_static.xml14
-rw-r--r--packages/SystemUI/res/values-night/colors.xml2
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml2
-rw-r--r--packages/SystemUI/res/values/colors.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml12
-rw-r--r--packages/SystemUI/res/values/dimens.xml8
-rw-r--r--packages/SystemUI/res/values/ids.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml26
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt28
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java4
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt19
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt15
-rw-r--r--packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt96
-rw-r--r--packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt76
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java60
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java74
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java179
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java162
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java142
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java238
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java565
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt213
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt282
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt189
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt130
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt140
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt178
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt)44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt97
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt121
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java93
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java153
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java120
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java (renamed from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java)7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt (renamed from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt)23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt)179
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt201
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java (renamed from packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java)28
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt76
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java99
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java225
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java269
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt123
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt216
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt270
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt181
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java478
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt)29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt)30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt)53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt)55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt)58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt222
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java169
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt)23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt)199
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt246
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java)84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt48
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt31
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt14
-rw-r--r--packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java40
-rw-r--r--proto/src/task_snapshot.proto48
-rw-r--r--proto/src/windowmanager.proto71
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java12
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java44
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java17
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java10
-rw-r--r--services/backup/java/com/android/server/backup/internal/BackupHandler.java11
-rw-r--r--services/backup/java/com/android/server/backup/transport/BackupTransportClient.java11
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java34
-rw-r--r--services/core/java/com/android/server/SystemService.java12
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java63
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java58
-rw-r--r--services/core/java/com/android/server/am/BroadcastConstants.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastLoopers.java109
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java70
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.md98
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java80
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags7
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java19
-rw-r--r--services/core/java/com/android/server/am/SameProcessApplicationThread.java73
-rw-r--r--services/core/java/com/android/server/am/UserController.java132
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java28
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java99
-rw-r--r--services/core/java/com/android/server/audio/BtHelper.java16
-rw-r--r--services/core/java/com/android/server/audio/FadeOutManager.java4
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java10
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java47
-rw-r--r--services/core/java/com/android/server/audio/RecordingActivityMonitor.java8
-rw-r--r--services/core/java/com/android/server/audio/SoundEffectsHelper.java2
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java4
-rw-r--r--services/core/java/com/android/server/biometrics/log/ALSProbe.java52
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java22
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java39
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java36
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java11
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java8
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java36
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java8
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java103
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java10
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java148
-rw-r--r--services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java279
-rw-r--r--services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java60
-rw-r--r--services/core/java/com/android/server/infra/ServiceNameBaseResolver.java325
-rw-r--r--services/core/java/com/android/server/input/BatteryController.java29
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java14
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java6
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java11
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java44
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java130
-rw-r--r--services/core/java/com/android/server/pm/InitAppsHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java349
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java377
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java29
-rw-r--r--services/core/java/com/android/server/pm/OWNERS12
-rw-r--r--services/core/java/com/android/server/pm/PrepareResult.java54
-rw-r--r--services/core/java/com/android/server/pm/ReconcilePackageUtils.java95
-rw-r--r--services/core/java/com/android/server/pm/ReconcileRequest.java59
-rw-r--r--services/core/java/com/android/server/pm/ReconciledPackage.java32
-rw-r--r--services/core/java/com/android/server/pm/ScanPackageUtils.java2
-rw-r--r--services/core/java/com/android/server/pm/ScanResult.java8
-rw-r--r--services/core/java/com/android/server/pm/Settings.java11
-rw-r--r--services/core/java/com/android/server/pm/SharedLibrariesImpl.java44
-rw-r--r--services/core/java/com/android/server/pm/StorageEventHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java303
-rw-r--r--services/core/java/com/android/server/pm/UserVisibilityMediator.java350
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java53
-rw-r--r--services/core/java/com/android/server/utils/EventLogger.java20
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java2
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java42
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java219
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java3
-rw-r--r--services/core/java/com/android/server/wm/InsetsControlTarget.java16
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java18
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java13
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java97
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java259
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java8
-rw-r--r--services/core/java/com/android/server/wm/Session.java21
-rw-r--r--services/core/java/com/android/server/wm/Task.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java12
-rw-r--r--services/core/java/com/android/server/wm/Transition.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java48
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp56
-rw-r--r--services/java/com/android/server/SystemServer.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java105
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java41
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java23
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt16
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java258
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java483
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java20
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java303
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java75
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java323
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt102
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java26
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ScanTests.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java69
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java263
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java172
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java15
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java11
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java24
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java17
-rw-r--r--telephony/java/android/telephony/ims/ImsService.java32
-rwxr-xr-xtelephony/java/com/android/internal/telephony/ISub.aidl8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt7
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp86
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp341
740 files changed, 20927 insertions, 10449 deletions
diff --git a/Android.bp b/Android.bp
index ed7a4813efe6..0315c12f95a1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -387,6 +387,7 @@ java_defaults {
"av-types-aidl-java",
"tv_tuner_resource_manager_aidl_interface-java",
"soundtrigger_middleware-aidl-java",
+ "modules-utils-binary-xml",
"modules-utils-build",
"modules-utils-preconditions",
"modules-utils-statemachine",
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index ab20fdbde1e5..98e4f4509b52 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -44,6 +44,8 @@ android_test {
"apct-perftests-utils",
"collector-device-lib",
"compatibility-device-util-axt",
+ "junit",
+ "junit-params",
"core-tests-support",
"guava",
],
diff --git a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
index 3f4f6af7554c..3ebaa4cd0bfa 100644
--- a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
@@ -20,18 +20,19 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class DeepArrayOpsPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -39,19 +40,14 @@ public class DeepArrayOpsPerfTest {
private Object[] mArray;
private Object[] mArray2;
- @Parameterized.Parameter(0)
- public int mArrayLength;
-
- @Parameterized.Parameters(name = "mArrayLength({0})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{1}, {4}, {16}, {32}, {2048}});
}
- @Before
- public void setUp() throws Exception {
- mArray = new Object[mArrayLength * 14];
- mArray2 = new Object[mArrayLength * 14];
- for (int i = 0; i < mArrayLength; i += 14) {
+ public void setUp(int arrayLength) throws Exception {
+ mArray = new Object[arrayLength * 14];
+ mArray2 = new Object[arrayLength * 14];
+ for (int i = 0; i < arrayLength; i += 14) {
mArray[i] = new IntWrapper(i);
mArray2[i] = new IntWrapper(i);
@@ -99,7 +95,9 @@ public class DeepArrayOpsPerfTest {
}
@Test
- public void deepHashCode() {
+ @Parameters(method = "getData")
+ public void deepHashCode(int arrayLength) throws Exception {
+ setUp(arrayLength);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Arrays.deepHashCode(mArray);
@@ -107,7 +105,9 @@ public class DeepArrayOpsPerfTest {
}
@Test
- public void deepEquals() {
+ @Parameters(method = "getData")
+ public void deepEquals(int arrayLength) throws Exception {
+ setUp(arrayLength);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Arrays.deepEquals(mArray, mArray2);
diff --git a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
index 5aacfc25bfd1..20f1309bd8e6 100644
--- a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
@@ -20,22 +20,22 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class SystemArrayCopyPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "arrayLength={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{2}, {4}, {8}, {16}, {32}, {64}, {128}, {256}, {512}, {1024}, {2048}, {4096},
@@ -43,12 +43,10 @@ public class SystemArrayCopyPerfTest {
});
}
- @Parameterized.Parameter(0)
- public int arrayLength;
-
// Provides benchmarking for different types of arrays using the arraycopy function.
@Test
- public void timeSystemCharArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemCharArrayCopy(int arrayLength) {
final int len = arrayLength;
char[] src = new char[len];
char[] dst = new char[len];
@@ -59,7 +57,8 @@ public class SystemArrayCopyPerfTest {
}
@Test
- public void timeSystemByteArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemByteArrayCopy(int arrayLength) {
final int len = arrayLength;
byte[] src = new byte[len];
byte[] dst = new byte[len];
@@ -70,7 +69,8 @@ public class SystemArrayCopyPerfTest {
}
@Test
- public void timeSystemShortArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemShortArrayCopy(int arrayLength) {
final int len = arrayLength;
short[] src = new short[len];
short[] dst = new short[len];
@@ -81,7 +81,8 @@ public class SystemArrayCopyPerfTest {
}
@Test
- public void timeSystemIntArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemIntArrayCopy(int arrayLength) {
final int len = arrayLength;
int[] src = new int[len];
int[] dst = new int[len];
@@ -92,7 +93,8 @@ public class SystemArrayCopyPerfTest {
}
@Test
- public void timeSystemLongArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemLongArrayCopy(int arrayLength) {
final int len = arrayLength;
long[] src = new long[len];
long[] dst = new long[len];
@@ -103,7 +105,8 @@ public class SystemArrayCopyPerfTest {
}
@Test
- public void timeSystemFloatArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemFloatArrayCopy(int arrayLength) {
final int len = arrayLength;
float[] src = new float[len];
float[] dst = new float[len];
@@ -114,7 +117,8 @@ public class SystemArrayCopyPerfTest {
}
@Test
- public void timeSystemDoubleArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemDoubleArrayCopy(int arrayLength) {
final int len = arrayLength;
double[] src = new double[len];
double[] dst = new double[len];
@@ -125,7 +129,8 @@ public class SystemArrayCopyPerfTest {
}
@Test
- public void timeSystemBooleanArrayCopy() {
+ @Parameters(method = "getData")
+ public void timeSystemBooleanArrayCopy(int arrayLength) {
final int len = arrayLength;
boolean[] src = new boolean[len];
boolean[] dst = new boolean[len];
diff --git a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
index eec0734cffda..b1b594d64324 100644
--- a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
@@ -20,42 +20,32 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.xmlpull.v1.XmlSerializer;
import java.io.CharArrayWriter;
import java.lang.reflect.Constructor;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Random;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class XmlSerializePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mDatasetAsString({0}), mSeed({1})")
- public static Collection<Object[]> data() {
- return Arrays.asList(
- new Object[][] {
- {"0.99 0.7 0.7 0.7 0.7 0.7", 854328},
- {"0.999 0.3 0.3 0.95 0.9 0.9", 854328},
- {"0.99 0.7 0.7 0.7 0.7 0.7", 312547},
- {"0.999 0.3 0.3 0.95 0.9 0.9", 312547}
- });
+ private Object[] getParams() {
+ return new Object[][] {
+ new Object[] {"0.99 0.7 0.7 0.7 0.7 0.7", 854328},
+ new Object[] {"0.999 0.3 0.3 0.95 0.9 0.9", 854328},
+ new Object[] {"0.99 0.7 0.7 0.7 0.7 0.7", 312547},
+ new Object[] {"0.999 0.3 0.3 0.95 0.9 0.9", 312547}
+ };
}
- @Parameterized.Parameter(0)
- public String mDatasetAsString;
-
- @Parameterized.Parameter(1)
- public int mSeed;
-
double[] mDataset;
private Constructor<? extends XmlSerializer> mKxmlConstructor;
private Constructor<? extends XmlSerializer> mFastConstructor;
@@ -100,8 +90,7 @@ public class XmlSerializePerfTest {
}
@SuppressWarnings("unchecked")
- @Before
- public void setUp() throws Exception {
+ public void setUp(String datasetAsString) throws Exception {
mKxmlConstructor =
(Constructor)
Class.forName("com.android.org.kxml2.io.KXmlSerializer").getConstructor();
@@ -109,28 +98,32 @@ public class XmlSerializePerfTest {
(Constructor)
Class.forName("com.android.internal.util.FastXmlSerializer")
.getConstructor();
- String[] splitStrings = mDatasetAsString.split(" ");
+ String[] splitStrings = datasetAsString.split(" ");
mDataset = new double[splitStrings.length];
for (int i = 0; i < splitStrings.length; i++) {
mDataset[i] = Double.parseDouble(splitStrings[i]);
}
}
- private void internalTimeSerializer(Constructor<? extends XmlSerializer> ctor)
+ private void internalTimeSerializer(Constructor<? extends XmlSerializer> ctor, int seed)
throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- serializeRandomXml(ctor, mSeed);
+ serializeRandomXml(ctor, seed);
}
}
@Test
- public void timeKxml() throws Exception {
- internalTimeSerializer(mKxmlConstructor);
+ @Parameters(method = "getParams")
+ public void timeKxml(String datasetAsString, int seed) throws Exception {
+ setUp(datasetAsString);
+ internalTimeSerializer(mKxmlConstructor, seed);
}
@Test
- public void timeFast() throws Exception {
- internalTimeSerializer(mFastConstructor);
+ @Parameters(method = "getParams")
+ public void timeFast(String datasetAsString, int seed) throws Exception {
+ setUp(datasetAsString);
+ internalTimeSerializer(mFastConstructor, seed);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
index 31c92ba5e207..3a45d4045d62 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
@@ -20,12 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.FileOutputStream;
@@ -38,23 +38,18 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ZipFilePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private File mFile;
- @Parameters(name = "numEntries={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{128}, {1024}, {8192}});
}
- @Parameterized.Parameter(0)
- public int numEntries;
-
- @Before
- public void setUp() throws Exception {
+ public void setUp(int numEntries) throws Exception {
mFile = File.createTempFile(getClass().getName(), ".zip");
mFile.deleteOnExit();
writeEntries(new ZipOutputStream(new FileOutputStream(mFile)), numEntries, 0);
@@ -66,7 +61,9 @@ public class ZipFilePerfTest {
}
@Test
- public void timeZipFileOpen() throws Exception {
+ @Parameters(method = "getData")
+ public void timeZipFileOpen(int numEntries) throws Exception {
+ setUp(numEntries);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
ZipFile zf = new ZipFile(mFile);
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
index faa96285cefd..2e89518ec9fb 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
@@ -20,12 +20,13 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.FileOutputStream;
@@ -39,21 +40,17 @@ import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ZipFileReadPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "readBufferSize={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{1024}, {16384}, {65536}});
}
private File mFile;
- @Parameterized.Parameter(0)
- public int readBufferSize;
-
@Before
public void setUp() throws Exception {
mFile = File.createTempFile(getClass().getName(), ".zip");
@@ -90,7 +87,8 @@ public class ZipFileReadPerfTest {
}
@Test
- public void timeZipFileRead() throws Exception {
+ @Parameters(method = "getData")
+ public void timeZipFileRead(int readBufferSize) throws Exception {
byte[] readBuffer = new byte[readBufferSize];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
index db5462cd69bf..2c0473eda830 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
@@ -20,96 +20,99 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class BitSetPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mSize={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{1000}, {10000}});
}
- @Parameterized.Parameter(0)
- public int mSize;
-
- private BitSet mBitSet;
-
- @Before
- public void setUp() throws Exception {
- mBitSet = new BitSet(mSize);
- }
-
@Test
- public void timeIsEmptyTrue() {
+ @Parameters(method = "getData")
+ public void timeIsEmptyTrue(int size) {
+ BitSet bitSet = new BitSet(size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- if (!mBitSet.isEmpty()) throw new RuntimeException();
+ if (!bitSet.isEmpty()) throw new RuntimeException();
}
}
@Test
- public void timeIsEmptyFalse() {
- mBitSet.set(mBitSet.size() - 1);
+ @Parameters(method = "getData")
+ public void timeIsEmptyFalse(int size) {
+ BitSet bitSet = new BitSet(size);
+ bitSet.set(bitSet.size() - 1);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- if (mBitSet.isEmpty()) throw new RuntimeException();
+ if (bitSet.isEmpty()) throw new RuntimeException();
}
}
@Test
- public void timeGet() {
+ @Parameters(method = "getData")
+ public void timeGet(int size) {
+ BitSet bitSet = new BitSet(size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
int i = 1;
while (state.keepRunning()) {
- mBitSet.get(++i % mSize);
+ bitSet.get(++i % size);
}
}
@Test
- public void timeClear() {
+ @Parameters(method = "getData")
+ public void timeClear(int size) {
+ BitSet bitSet = new BitSet(size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
int i = 1;
while (state.keepRunning()) {
- mBitSet.clear(++i % mSize);
+ bitSet.clear(++i % size);
}
}
@Test
- public void timeSet() {
+ @Parameters(method = "getData")
+ public void timeSet(int size) {
+ BitSet bitSet = new BitSet(size);
int i = 1;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mBitSet.set(++i % mSize);
+ bitSet.set(++i % size);
}
}
@Test
- public void timeSetOn() {
+ @Parameters(method = "getData")
+ public void timeSetOn(int size) {
+ BitSet bitSet = new BitSet(size);
int i = 1;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mBitSet.set(++i % mSize, true);
+ bitSet.set(++i % size, true);
}
}
@Test
- public void timeSetOff() {
+ @Parameters(method = "getData")
+ public void timeSetOff(int size) {
+ BitSet bitSet = new BitSet(size);
int i = 1;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mBitSet.set(++i % mSize, false);
+ bitSet.set(++i % size, false);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
index 3952c12b3bfe..6a2ce5847daa 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
@@ -20,18 +20,19 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class BreakIteratorPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -41,36 +42,37 @@ public final class BreakIteratorPerfTest {
Locale.US,
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis consequat"
+ " nisl non pharetra. Praesent pretium vehicula odio sed ultrices. Aenean a"
- + " felis libero. Vivamus sed commodo nibh. Pellentesque turpis lectus, euismod"
- + " vel ante nec, cursus posuere orci. Suspendisse velit neque, fermentum"
- + " luctus ultrices in, ultrices vitae arcu. Duis tincidunt cursus lorem. Nam"
- + " ultricies accumsan quam vitae imperdiet. Pellentesque habitant morbi"
- + " tristique senectus et netus et malesuada fames ac turpis egestas. Quisque"
- + " aliquet pretium nisi, eget laoreet enim molestie sit amet. Class aptent"
- + " taciti sociosqu ad litora torquent per conubia nostra, per inceptos"
+ + " felis libero. Vivamus sed commodo nibh. Pellentesque turpis lectus,"
+ + " euismod vel ante nec, cursus posuere orci. Suspendisse velit neque,"
+ + " fermentum luctus ultrices in, ultrices vitae arcu. Duis tincidunt cursus"
+ + " lorem. Nam ultricies accumsan quam vitae imperdiet. Pellentesque habitant"
+ + " morbi tristique senectus et netus et malesuada fames ac turpis egestas."
+ + " Quisque aliquet pretium nisi, eget laoreet enim molestie sit amet. Class"
+ + " aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos"
+ " himenaeos.\n"
+ "Nam dapibus aliquam lacus ac suscipit. Proin in nibh sit amet purus congue"
+ " laoreet eget quis nisl. Morbi gravida dignissim justo, a venenatis ante"
- + " pulvinar at. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin"
- + " ultrices vestibulum dui, vel aliquam lacus aliquam quis. Duis fringilla"
- + " sapien ac lacus egestas, vel adipiscing elit euismod. Donec non tellus"
- + " odio. Donec gravida eu massa ac feugiat. Aliquam erat volutpat. Praesent id"
- + " adipiscing metus, nec laoreet enim. Aliquam vitae posuere turpis. Mauris ac"
- + " pharetra sem. In at placerat tortor. Vivamus ac vehicula neque. Cras"
- + " volutpat ullamcorper massa et varius. Praesent sagittis neque vitae nulla"
- + " euismod pharetra.\n"
+ + " pulvinar at. Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+ + " Proin ultrices vestibulum dui, vel aliquam lacus aliquam quis. Duis"
+ + " fringilla sapien ac lacus egestas, vel adipiscing elit euismod. Donec non"
+ + " tellus odio. Donec gravida eu massa ac feugiat. Aliquam erat volutpat."
+ + " Praesent id adipiscing metus, nec laoreet enim. Aliquam vitae posuere"
+ + " turpis. Mauris ac pharetra sem. In at placerat tortor. Vivamus ac vehicula"
+ + " neque. Cras volutpat ullamcorper massa et varius. Praesent sagittis neque"
+ + " vitae nulla euismod pharetra.\n"
+ "Sed placerat sapien non molestie sollicitudin. Nullam sit amet dictum quam."
+ " Etiam tincidunt tortor vel pretium vehicula. Praesent fringilla ipsum vel"
+ " velit luctus dignissim. Nulla massa ligula, mattis in enim et, mattis"
+ " lacinia odio. Suspendisse tristique urna a orci commodo tempor. Duis"
+ " lacinia egestas arcu a sollicitudin.\n"
+ "In ac feugiat lacus. Nunc fermentum eu est at tristique. Pellentesque quis"
- + " ligula et orci placerat lacinia. Maecenas quis mauris diam. Etiam mi ipsum,"
- + " tempus in purus quis, euismod faucibus orci. Nulla facilisi. Praesent sit"
- + " amet sapien vel elit porta adipiscing. Phasellus sit amet volutpat diam.\n"
- + "Proin bibendum elit non lacus pharetra, quis eleifend tellus placerat. Nulla"
- + " facilisi. Maecenas ante diam, pellentesque mattis mattis in, porta ut"
- + " lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices"
+ + " ligula et orci placerat lacinia. Maecenas quis mauris diam. Etiam mi"
+ + " ipsum, tempus in purus quis, euismod faucibus orci. Nulla facilisi."
+ + " Praesent sit amet sapien vel elit porta adipiscing. Phasellus sit amet"
+ + " volutpat diam.\n"
+ + "Proin bibendum elit non lacus pharetra, quis eleifend tellus placerat."
+ + " Nulla facilisi. Maecenas ante diam, pellentesque mattis mattis in, porta"
+ + " ut lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices"
+ " posuere cubilia Curae; Nunc interdum tristique metus, in scelerisque odio"
+ " fermentum eget. Cras nec venenatis lacus. Aenean euismod eget metus quis"
+ " molestie. Cras tincidunt dolor ut massa ornare, in elementum lacus auctor."
@@ -80,29 +82,29 @@ public final class BreakIteratorPerfTest {
LONGPARA(
Locale.US,
"During dinner, Mr. Bennet scarcely spoke at all; but when the servants were"
- + " withdrawn, he thought it time to have some conversation with his guest, and"
- + " therefore started a subject in which he expected him to shine, by observing"
- + " that he seemed very fortunate in his patroness. Lady Catherine de Bourgh's"
- + " attention to his wishes, and consideration for his comfort, appeared very"
- + " remarkable. Mr. Bennet could not have chosen better. Mr. Collins was"
- + " eloquent in her praise. The subject elevated him to more than usual"
- + " solemnity of manner, and with a most important aspect he protested that"
- + " \"he had never in his life witnessed such behaviour in a person of"
- + " rank--such affability and condescension, as he had himself experienced from"
- + " Lady Catherine. She had been graciously pleased to approve of both of the"
- + " discourses which he had already had the honour of preaching before her. She"
- + " had also asked him twice to dine at Rosings, and had sent for him only the"
- + " Saturday before, to make up her pool of quadrille in the evening. Lady"
- + " Catherine was reckoned proud by many people he knew, but _he_ had never"
- + " seen anything but affability in her. She had always spoken to him as she"
- + " would to any other gentleman; she made not the smallest objection to his"
- + " joining in the society of the neighbourhood nor to his leaving the parish"
- + " occasionally for a week or two, to visit his relations. She had even"
- + " condescended to advise him to marry as soon as he could, provided he chose"
- + " with discretion; and had once paid him a visit in his humble parsonage,"
- + " where she had perfectly approved all the alterations he had been making,"
- + " and had even vouchsafed to suggest some herself--some shelves in the closet"
- + " up stairs.\""),
+ + " withdrawn, he thought it time to have some conversation with his guest,"
+ + " and therefore started a subject in which he expected him to shine, by"
+ + " observing that he seemed very fortunate in his patroness. Lady Catherine"
+ + " de Bourgh's attention to his wishes, and consideration for his comfort,"
+ + " appeared very remarkable. Mr. Bennet could not have chosen better. Mr."
+ + " Collins was eloquent in her praise. The subject elevated him to more than"
+ + " usual solemnity of manner, and with a most important aspect he protested"
+ + " that \"he had never in his life witnessed such behaviour in a person of"
+ + " rank--such affability and condescension, as he had himself experienced"
+ + " from Lady Catherine. She had been graciously pleased to approve of both of"
+ + " the discourses which he had already had the honour of preaching before"
+ + " her. She had also asked him twice to dine at Rosings, and had sent for him"
+ + " only the Saturday before, to make up her pool of quadrille in the evening."
+ + " Lady Catherine was reckoned proud by many people he knew, but _he_ had"
+ + " never seen anything but affability in her. She had always spoken to him as"
+ + " she would to any other gentleman; she made not the smallest objection to"
+ + " his joining in the society of the neighbourhood nor to his leaving the"
+ + " parish occasionally for a week or two, to visit his relations. She had"
+ + " even condescended to advise him to marry as soon as he could, provided he"
+ + " chose with discretion; and had once paid him a visit in his humble"
+ + " parsonage, where she had perfectly approved all the alterations he had"
+ + " been making, and had even vouchsafed to suggest some herself--some shelves"
+ + " in the closet up stairs.\""),
GERMAN(
Locale.GERMANY,
"Aber dieser Freiheit setzte endlich der Winter ein Ziel. Draußen auf den Feldern"
@@ -119,15 +121,14 @@ public final class BreakIteratorPerfTest {
+ " เดิมทีเป็นการผสมผสานกันระหว่างสำเนียงอยุธยาและชาวไทยเชื้อสายจีนรุ่นหลังที่"
+ "พูดไทยแทนกลุ่มภาษาจีน"
+ " ลักษณะเด่นคือมีการออกเสียงที่ชัดเจนและแข็งกระด้างซึ่งได้รับอิทธิพลจากภาษาแต"
- + "้จิ๋ว"
- + " การออกเสียงพยัญชนะ สระ การผันวรรณยุกต์ที่ในภาษาไทยมาตรฐาน"
+ + "้จิ๋ว การออกเสียงพยัญชนะ สระ การผันวรรณยุกต์ที่ในภาษาไทยมาตรฐาน"
+ " มาจากสำเนียงถิ่นนี้ในขณะที่ภาษาไทยสำเนียงอื่นล้วนเหน่อทั้งสิ้น"
+ " คำศัพท์ที่ใช้ในสำเนียงกรุงเทพจำนวนมากได้รับมาจากกลุ่มภาษาจีนเช่นคำว่า โป๊,"
+ " เฮ็ง, อาหมวย, อาซิ่ม ซึ่งมาจากภาษาแต้จิ๋ว และจากภาษาจีนเช่น ถู(涂), ชิ่ว(去"
+ " อ่านว่า\"ชู่\") และคำว่า ทาย(猜 อ่านว่า \"ชาย\") เป็นต้น"
+ " เนื่องจากสำเนียงกรุงเทพได้รับอิทธิพลมาจากภาษาจีนดังนั้นตัวอักษร \"ร\""
- + " มักออกเสียงเหมารวมเป็น \"ล\" หรือคำควบกล่ำบางคำถูกละทิ้งไปด้วยเช่น รู้ เป็น"
- + " ลู้, เรื่อง เป็น เลื่อง หรือ ประเทศ เป็น ปะเทศ"
+ + " มักออกเสียงเหมารวมเป็น \"ล\" หรือคำควบกล่ำบางคำถูกละทิ้งไปด้วยเช่น รู้"
+ + " เป็น ลู้, เรื่อง เป็น เลื่อง หรือ ประเทศ เป็น ปะเทศ"
+ " เป็นต้นสร้างความลำบากให้แก่ต่างชาติที่ต้องการเรียนภาษาไทย"
+ " แต่อย่างไรก็ตามผู้ที่พูดสำเนียงถิ่นนี้ก็สามารถออกอักขระภาษาไทยตามมาตรฐานได"
+ "้อย่างถูกต้องเพียงแต่มักเผลอไม่ค่อยออกเสียง"),
@@ -151,8 +152,7 @@ public final class BreakIteratorPerfTest {
}
}
- @Parameters(name = "mText={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Text.ACCENT}, {Text.BIDI}, {Text.EMOJI}, {Text.EMPTY}, {Text.GERMAN},
@@ -161,15 +161,13 @@ public final class BreakIteratorPerfTest {
});
}
- @Parameterized.Parameter(0)
- public Text mText;
-
@Test
- public void timeBreakIterator() {
+ @Parameters(method = "getData")
+ public void timeBreakIterator(Text text) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- BreakIterator it = BreakIterator.getLineInstance(mText.mLocale);
- it.setText(mText.mText);
+ BreakIterator it = BreakIterator.getLineInstance(text.mLocale);
+ it.setText(text.mText);
while (it.next() != BreakIterator.DONE) {
// Keep iterating
@@ -178,12 +176,13 @@ public final class BreakIteratorPerfTest {
}
@Test
- public void timeIcuBreakIterator() {
+ @Parameters(method = "getData")
+ public void timeIcuBreakIterator(Text text) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
android.icu.text.BreakIterator it =
- android.icu.text.BreakIterator.getLineInstance(mText.mLocale);
- it.setText(mText.mText);
+ android.icu.text.BreakIterator.getLineInstance(text.mLocale);
+ it.setText(text.mText);
while (it.next() != android.icu.text.BreakIterator.DONE) {
// Keep iterating
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
index 855bb9a43d34..b7b7e83f147c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
@@ -20,11 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.IOException;
@@ -34,13 +35,12 @@ import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class BulkPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlign({0}), mSBuf({1}), mDBuf({2}), mSize({3})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{true, MyBufferType.DIRECT, MyBufferType.DIRECT, 4096},
@@ -82,24 +82,12 @@ public class BulkPerfTest {
});
}
- @Parameterized.Parameter(0)
- public boolean mAlign;
-
enum MyBufferType {
DIRECT,
HEAP,
MAPPED
}
- @Parameterized.Parameter(1)
- public MyBufferType mSBuf;
-
- @Parameterized.Parameter(2)
- public MyBufferType mDBuf;
-
- @Parameterized.Parameter(3)
- public int mSize;
-
public static ByteBuffer newBuffer(boolean aligned, MyBufferType bufferType, int bsize)
throws IOException {
int size = aligned ? bsize : bsize + 8 + 1;
@@ -126,13 +114,15 @@ public class BulkPerfTest {
}
@Test
- public void timePut() throws Exception {
- ByteBuffer src = BulkPerfTest.newBuffer(mAlign, mSBuf, mSize);
- ByteBuffer data = BulkPerfTest.newBuffer(mAlign, mDBuf, mSize);
+ @Parameters(method = "getData")
+ public void timePut(boolean align, MyBufferType sBuf, MyBufferType dBuf, int size)
+ throws Exception {
+ ByteBuffer src = BulkPerfTest.newBuffer(align, sBuf, size);
+ ByteBuffer data = BulkPerfTest.newBuffer(align, dBuf, size);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAlign ? 0 : 1);
- data.position(mAlign ? 0 : 1);
+ src.position(align ? 0 : 1);
+ data.position(align ? 0 : 1);
src.put(data);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
index 4bd7c4e4fa82..9ac36d076c7b 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
@@ -20,11 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.io.File;
import java.io.IOException;
@@ -41,7 +42,7 @@ import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ByteBufferPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -49,15 +50,14 @@ public class ByteBufferPerfTest {
public enum MyByteOrder {
BIG(ByteOrder.BIG_ENDIAN),
LITTLE(ByteOrder.LITTLE_ENDIAN);
- final ByteOrder mByteOrder;
+ final ByteOrder byteOrder;
- MyByteOrder(ByteOrder mByteOrder) {
- this.mByteOrder = mByteOrder;
+ MyByteOrder(ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
}
}
- @Parameters(name = "mByteOrder={0}, mAligned={1}, mBufferType={2}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{MyByteOrder.BIG, true, MyBufferType.DIRECT},
@@ -75,21 +75,12 @@ public class ByteBufferPerfTest {
});
}
- @Parameterized.Parameter(0)
- public MyByteOrder mByteOrder;
-
- @Parameterized.Parameter(1)
- public boolean mAligned;
-
enum MyBufferType {
DIRECT,
HEAP,
MAPPED;
}
- @Parameterized.Parameter(2)
- public MyBufferType mBufferType;
-
public static ByteBuffer newBuffer(
MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws IOException {
int size = aligned ? 8192 : 8192 + 8 + 1;
@@ -115,7 +106,7 @@ public class ByteBufferPerfTest {
result = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
break;
}
- result.order(byteOrder.mByteOrder);
+ result.order(byteOrder.byteOrder);
result.position(aligned ? 0 : 1);
return result;
}
@@ -125,11 +116,13 @@ public class ByteBufferPerfTest {
//
@Test
- public void timeByteBuffer_getByte() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getByte(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.get();
}
@@ -137,24 +130,28 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getByteArray() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getByteArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
byte[] dst = new byte[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
for (int i = 0; i < 1024; ++i) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
src.get(dst);
}
}
}
@Test
- public void timeByteBuffer_getByte_indexed() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getByte_indexed(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.get(i);
}
@@ -162,11 +159,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getChar() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getChar(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getChar();
}
@@ -174,9 +173,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeCharBuffer_getCharArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeCharBuffer_getCharArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
CharBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asCharBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
char[] dst = new char[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -188,11 +189,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getChar_indexed() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getChar_indexed(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getChar(i * 2);
}
@@ -200,11 +203,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getDouble() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getDouble(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getDouble();
}
@@ -212,9 +217,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeDoubleBuffer_getDoubleArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeDoubleBuffer_getDoubleArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
DoubleBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asDoubleBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
double[] dst = new double[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -226,11 +233,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getFloat() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getFloat(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getFloat();
}
@@ -238,9 +247,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeFloatBuffer_getFloatArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeFloatBuffer_getFloatArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
FloatBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asFloatBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
float[] dst = new float[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -252,11 +263,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getInt() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getInt(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getInt();
}
@@ -264,9 +277,10 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeIntBuffer_getIntArray() throws Exception {
- IntBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asIntBuffer();
+ @Parameters(method = "getData")
+ public void timeIntBuffer_getIntArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ IntBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
int[] dst = new int[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -278,11 +292,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getLong() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getLong(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getLong();
}
@@ -290,9 +306,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeLongBuffer_getLongArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLongBuffer_getLongArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
LongBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asLongBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
long[] dst = new long[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -304,11 +322,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_getShort() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_getShort(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.getShort();
}
@@ -316,9 +336,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeShortBuffer_getShortArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeShortBuffer_getShortArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
ShortBuffer src =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asShortBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
short[] dst = new short[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -334,8 +356,10 @@ public class ByteBufferPerfTest {
//
@Test
- public void timeByteBuffer_putByte() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putByte(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
src.position(0);
@@ -346,24 +370,28 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_putByteArray() throws Exception {
- ByteBuffer dst = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putByteArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
byte[] src = new byte[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
for (int i = 0; i < 1024; ++i) {
- dst.position(mAligned ? 0 : 1);
+ dst.position(aligned ? 0 : 1);
dst.put(src);
}
}
}
@Test
- public void timeByteBuffer_putChar() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putChar(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putChar(' ');
}
@@ -371,9 +399,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeCharBuffer_putCharArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeCharBuffer_putCharArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
CharBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asCharBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
char[] src = new char[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -385,11 +415,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_putDouble() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putDouble(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putDouble(0.0);
}
@@ -397,9 +429,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeDoubleBuffer_putDoubleArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeDoubleBuffer_putDoubleArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
DoubleBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asDoubleBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
double[] src = new double[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -411,11 +445,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_putFloat() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putFloat(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putFloat(0.0f);
}
@@ -423,9 +459,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeFloatBuffer_putFloatArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeFloatBuffer_putFloatArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
FloatBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asFloatBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
float[] src = new float[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -437,11 +475,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_putInt() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putInt(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putInt(0);
}
@@ -449,9 +489,10 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeIntBuffer_putIntArray() throws Exception {
- IntBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asIntBuffer();
+ @Parameters(method = "getData")
+ public void timeIntBuffer_putIntArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ IntBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
int[] src = new int[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -463,11 +504,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_putLong() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putLong(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putLong(0L);
}
@@ -475,9 +518,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeLongBuffer_putLongArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLongBuffer_putLongArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
LongBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asLongBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
long[] src = new long[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -489,11 +534,13 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeByteBuffer_putShort() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeByteBuffer_putShort(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
for (int i = 0; i < 1024; ++i) {
src.putShort((short) 0);
}
@@ -501,9 +548,11 @@ public class ByteBufferPerfTest {
}
@Test
- public void timeShortBuffer_putShortArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeShortBuffer_putShortArray(
+ MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
ShortBuffer dst =
- ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asShortBuffer();
+ ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
short[] src = new short[1024];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -515,6 +564,7 @@ public class ByteBufferPerfTest {
}
@Test
+ @Parameters(method = "getData")
public void time_new_byteArray() throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -523,6 +573,7 @@ public class ByteBufferPerfTest {
}
@Test
+ @Parameters(method = "getData")
public void time_ByteBuffer_allocate() throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
index 81f9e59f2423..5dd9d6e2bc2c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
@@ -20,23 +20,23 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class ByteBufferScalarVersusVectorPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mByteOrder={0}, mAligned={1}, mBufferType={2}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{
@@ -102,19 +102,15 @@ public class ByteBufferScalarVersusVectorPerfTest {
});
}
- @Parameterized.Parameter(0)
- public ByteBufferPerfTest.MyByteOrder mByteOrder;
-
- @Parameterized.Parameter(1)
- public boolean mAligned;
-
- @Parameterized.Parameter(2)
- public ByteBufferPerfTest.MyBufferType mBufferType;
-
@Test
- public void timeManualByteBufferCopy() throws Exception {
- ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
- ByteBuffer dst = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+ @Parameters(method = "getData")
+ public void timeManualByteBufferCopy(
+ ByteBufferPerfTest.MyByteOrder byteOrder,
+ boolean aligned,
+ ByteBufferPerfTest.MyBufferType bufferType)
+ throws Exception {
+ ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
+ ByteBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
src.position(0);
@@ -126,23 +122,25 @@ public class ByteBufferScalarVersusVectorPerfTest {
}
@Test
- public void timeByteBufferBulkGet() throws Exception {
- ByteBuffer src = ByteBuffer.allocate(mAligned ? 8192 : 8192 + 1);
+ @Parameters({"true", "false"})
+ public void timeByteBufferBulkGet(boolean aligned) throws Exception {
+ ByteBuffer src = ByteBuffer.allocate(aligned ? 8192 : 8192 + 1);
byte[] dst = new byte[8192];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
src.get(dst, 0, dst.length);
}
}
@Test
- public void timeDirectByteBufferBulkGet() throws Exception {
- ByteBuffer src = ByteBuffer.allocateDirect(mAligned ? 8192 : 8192 + 1);
+ @Parameters({"true", "false"})
+ public void timeDirectByteBufferBulkGet(boolean aligned) throws Exception {
+ ByteBuffer src = ByteBuffer.allocateDirect(aligned ? 8192 : 8192 + 1);
byte[] dst = new byte[8192];
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- src.position(mAligned ? 0 : 1);
+ src.position(aligned ? 0 : 1);
src.get(dst, 0, dst.length);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
index 28ec6ded3c86..0a598998bced 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
@@ -20,12 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
@@ -34,13 +34,12 @@ import java.util.Collection;
* Tests various Character methods, intended for testing multiple implementations against each
* other.
*/
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CharacterPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mCharacterSet({0}), mOverload({1})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{CharacterSet.ASCII, Overload.CHAR},
@@ -50,17 +49,10 @@ public class CharacterPerfTest {
});
}
- @Parameterized.Parameter(0)
- public CharacterSet mCharacterSet;
-
- @Parameterized.Parameter(1)
- public Overload mOverload;
-
private char[] mChars;
- @Before
- public void setUp() throws Exception {
- this.mChars = mCharacterSet.mChars;
+ public void setUp(CharacterSet characterSet) {
+ this.mChars = characterSet.mChars;
}
public enum Overload {
@@ -87,10 +79,12 @@ public class CharacterPerfTest {
// A fake benchmark to give us a baseline.
@Test
- public void timeIsSpace() {
+ @Parameters(method = "getData")
+ public void timeIsSpace(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
boolean fake = false;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
fake ^= ((char) ch == ' ');
@@ -106,9 +100,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeDigit() {
+ @Parameters(method = "getData")
+ public void timeDigit(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.digit(mChars[ch], 10);
@@ -124,9 +120,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeGetNumericValue() {
+ @Parameters(method = "getData")
+ public void timeGetNumericValue(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.getNumericValue(mChars[ch]);
@@ -142,9 +140,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsDigit() {
+ @Parameters(method = "getData")
+ public void timeIsDigit(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isDigit(mChars[ch]);
@@ -160,9 +160,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsIdentifierIgnorable() {
+ @Parameters(method = "getData")
+ public void timeIsIdentifierIgnorable(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isIdentifierIgnorable(mChars[ch]);
@@ -178,9 +180,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsJavaIdentifierPart() {
+ @Parameters(method = "getData")
+ public void timeIsJavaIdentifierPart(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isJavaIdentifierPart(mChars[ch]);
@@ -196,9 +200,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsJavaIdentifierStart() {
+ @Parameters(method = "getData")
+ public void timeIsJavaIdentifierStart(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isJavaIdentifierStart(mChars[ch]);
@@ -214,9 +220,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsLetter() {
+ @Parameters(method = "getData")
+ public void timeIsLetter(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isLetter(mChars[ch]);
@@ -232,9 +240,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsLetterOrDigit() {
+ @Parameters(method = "getData")
+ public void timeIsLetterOrDigit(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isLetterOrDigit(mChars[ch]);
@@ -250,9 +260,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsLowerCase() {
+ @Parameters(method = "getData")
+ public void timeIsLowerCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isLowerCase(mChars[ch]);
@@ -268,9 +280,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsSpaceChar() {
+ @Parameters(method = "getData")
+ public void timeIsSpaceChar(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isSpaceChar(mChars[ch]);
@@ -286,9 +300,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsUpperCase() {
+ @Parameters(method = "getData")
+ public void timeIsUpperCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isUpperCase(mChars[ch]);
@@ -304,9 +320,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeIsWhitespace() {
+ @Parameters(method = "getData")
+ public void timeIsWhitespace(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.isWhitespace(mChars[ch]);
@@ -322,9 +340,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeToLowerCase() {
+ @Parameters(method = "getData")
+ public void timeToLowerCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.toLowerCase(mChars[ch]);
@@ -340,9 +360,11 @@ public class CharacterPerfTest {
}
@Test
- public void timeToUpperCase() {
+ @Parameters(method = "getData")
+ public void timeToUpperCase(CharacterSet characterSet, Overload overload) {
+ setUp(characterSet);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- if (mOverload == Overload.CHAR) {
+ if (overload == Overload.CHAR) {
while (state.keepRunning()) {
for (int ch = 0; ch < 65536; ++ch) {
Character.toUpperCase(mChars[ch]);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
index 603b182e7c36..8da13a9b7f91 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
@@ -20,44 +20,40 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CharsetForNamePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "mCharsetName({0})")
- public static Collection<Object[]> data() {
- return Arrays.asList(
- new Object[][] {
- {"UTF-16"},
- {"UTF-8"},
- {"UTF8"},
- {"ISO-8859-1"},
- {"8859_1"},
- {"ISO-8859-2"},
- {"8859_2"},
- {"US-ASCII"},
- {"ASCII"},
- });
+ public static String[] charsetNames() {
+ return new String[] {
+ "UTF-16",
+ "UTF-8",
+ "UTF8",
+ "ISO-8859-1",
+ "8859_1",
+ "ISO-8859-2",
+ "8859_2",
+ "US-ASCII",
+ "ASCII",
+ };
}
- @Parameterized.Parameter(0)
- public String mCharsetName;
-
@Test
- public void timeCharsetForName() throws Exception {
+ @Parameters(method = "charsetNames")
+ public void timeCharsetForName(String charsetName) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- Charset.forName(mCharsetName);
+ Charset.forName(charsetName);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
index 437d186834e0..048c50f044c8 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
@@ -20,22 +20,22 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CharsetPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mLength({0}), mName({1})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{1, "UTF-16"},
@@ -86,24 +86,20 @@ public class CharsetPerfTest {
});
}
- @Parameterized.Parameter(0)
- public int mLength;
-
- @Parameterized.Parameter(1)
- public String mName;
-
@Test
- public void time_new_String_BString() throws Exception {
- byte[] bytes = makeBytes(makeString(mLength));
+ @Parameters(method = "getData")
+ public void time_new_String_BString(int length, String name) throws Exception {
+ byte[] bytes = makeBytes(makeString(length));
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- new String(bytes, mName);
+ new String(bytes, name);
}
}
@Test
- public void time_new_String_BII() throws Exception {
- byte[] bytes = makeBytes(makeString(mLength));
+ @Parameters(method = "getData")
+ public void time_new_String_BII(int length, String name) throws Exception {
+ byte[] bytes = makeBytes(makeString(length));
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
new String(bytes, 0, bytes.length);
@@ -111,20 +107,22 @@ public class CharsetPerfTest {
}
@Test
- public void time_new_String_BIIString() throws Exception {
- byte[] bytes = makeBytes(makeString(mLength));
+ @Parameters(method = "getData")
+ public void time_new_String_BIIString(int length, String name) throws Exception {
+ byte[] bytes = makeBytes(makeString(length));
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- new String(bytes, 0, bytes.length, mName);
+ new String(bytes, 0, bytes.length, name);
}
}
@Test
- public void time_String_getBytes() throws Exception {
- String string = makeString(mLength);
+ @Parameters(method = "getData")
+ public void time_String_getBytes(int length, String name) throws Exception {
+ String string = makeString(length);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- string.getBytes(mName);
+ string.getBytes(name);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
index 15c27f2366e1..42b058815bfe 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
@@ -20,11 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList;
@@ -42,17 +43,13 @@ import javax.crypto.spec.IvParameterSpec;
* Cipher benchmarks. Only runs on AES currently because of the combinatorial explosion of the test
* as it stands.
*/
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CipherPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(
- name =
- "mMode({0}), mPadding({1}), mKeySize({2}), mInputSize({3}),"
- + " mImplementation({4})")
- public static Collection cases() {
- int[] mKeySizes = new int[] {128, 192, 256};
+ public static Collection getCases() {
+ int[] keySizes = new int[] {128, 192, 256};
int[] inputSizes = new int[] {16, 32, 64, 128, 1024, 8192};
final List<Object[]> params = new ArrayList<>();
for (Mode mode : Mode.values()) {
@@ -71,11 +68,11 @@ public class CipherPerfTest {
&& implementation == Implementation.OpenSSL) {
continue;
}
- for (int mKeySize : mKeySizes) {
+ for (int keySize : keySizes) {
for (int inputSize : inputSizes) {
params.add(
new Object[] {
- mode, padding, mKeySize, inputSize, implementation
+ mode, padding, keySize, inputSize, implementation
});
}
}
@@ -107,9 +104,6 @@ public class CipherPerfTest {
AES,
};
- @Parameterized.Parameter(0)
- public Mode mMode;
-
public enum Mode {
CBC,
CFB,
@@ -118,23 +112,11 @@ public class CipherPerfTest {
OFB,
};
- @Parameterized.Parameter(1)
- public Padding mPadding;
-
public enum Padding {
NOPADDING,
PKCS1PADDING,
};
- @Parameterized.Parameter(2)
- public int mKeySize;
-
- @Parameterized.Parameter(3)
- public int mInputSize;
-
- @Parameterized.Parameter(4)
- public Implementation mImplementation;
-
public enum Implementation {
OpenSSL,
BouncyCastle
@@ -156,21 +138,20 @@ public class CipherPerfTest {
private AlgorithmParameterSpec mSpec;
- @Before
- public void setUp() throws Exception {
- mCipherAlgorithm =
- mAlgorithm.toString() + "/" + mMode.toString() + "/" + mPadding.toString();
+ public void setUp(Mode mode, Padding padding, int keySize, Implementation implementation)
+ throws Exception {
+ mCipherAlgorithm = mAlgorithm.toString() + "/" + mode.toString() + "/" + padding.toString();
String mKeyAlgorithm = mAlgorithm.toString();
- mKey = sKeySizes.get(mKeySize);
+ mKey = sKeySizes.get(keySize);
if (mKey == null) {
KeyGenerator generator = KeyGenerator.getInstance(mKeyAlgorithm);
- generator.init(mKeySize);
+ generator.init(keySize);
mKey = generator.generateKey();
- sKeySizes.put(mKeySize, mKey);
+ sKeySizes.put(keySize, mKey);
}
- switch (mImplementation) {
+ switch (implementation) {
case OpenSSL:
mProviderName = "AndroidOpenSSL";
break;
@@ -178,10 +159,10 @@ public class CipherPerfTest {
mProviderName = "BC";
break;
default:
- throw new RuntimeException(mImplementation.toString());
+ throw new RuntimeException(implementation.toString());
}
- if (mMode != Mode.ECB) {
+ if (mode != Mode.ECB) {
mSpec = new IvParameterSpec(IV);
}
@@ -193,18 +174,26 @@ public class CipherPerfTest {
}
@Test
- public void timeEncrypt() throws Exception {
+ @Parameters(method = "getCases")
+ public void timeEncrypt(
+ Mode mode, Padding padding, int keySize, int inputSize, Implementation implementation)
+ throws Exception {
+ setUp(mode, padding, keySize, implementation);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mCipherEncrypt.doFinal(DATA, 0, mInputSize, mOutput);
+ mCipherEncrypt.doFinal(DATA, 0, inputSize, mOutput);
}
}
@Test
- public void timeDecrypt() throws Exception {
+ @Parameters(method = "getCases")
+ public void timeDecrypt(
+ Mode mode, Padding padding, int keySize, int inputSize, Implementation implementation)
+ throws Exception {
+ setUp(mode, padding, keySize, implementation);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mCipherDecrypt.doFinal(DATA, 0, mInputSize, mOutput);
+ mCipherDecrypt.doFinal(DATA, 0, inputSize, mOutput);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
index a89efffcdd1f..69197c3325f4 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
@@ -20,11 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.ArrayList;
import java.util.Arrays;
@@ -35,19 +36,15 @@ import java.util.List;
import java.util.Random;
import java.util.Vector;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class CollectionsPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mArrayListLength({0})")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{4}, {16}, {64}, {256}, {1024}});
}
- @Parameterized.Parameter(0)
- public int arrayListLength;
-
public static Comparator<Integer> REVERSE =
new Comparator<Integer>() {
@Override
@@ -59,7 +56,8 @@ public class CollectionsPerfTest {
};
@Test
- public void timeSort_arrayList() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSort_arrayList(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, ArrayList.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -68,7 +66,8 @@ public class CollectionsPerfTest {
}
@Test
- public void timeSortWithComparator_arrayList() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSortWithComparator_arrayList(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, ArrayList.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -77,7 +76,8 @@ public class CollectionsPerfTest {
}
@Test
- public void timeSort_vector() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSort_vector(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, Vector.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
@@ -86,7 +86,8 @@ public class CollectionsPerfTest {
}
@Test
- public void timeSortWithComparator_vector() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSortWithComparator_vector(int arrayListLength) throws Exception {
List<Integer> input = buildList(arrayListLength, Vector.class);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
index 4ff3ba5b0aaa..839120336697 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
@@ -20,11 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
import java.net.URI;
import java.net.URL;
@@ -32,39 +33,39 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class EqualsHashCodePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private enum Type {
URI() {
- @Override Object newInstance(String text) throws Exception {
+ @Override
+ Object newInstance(String text) throws Exception {
return new URI(text);
}
},
URL() {
- @Override Object newInstance(String text) throws Exception {
+ @Override
+ Object newInstance(String text) throws Exception {
return new URL(text);
}
};
+
abstract Object newInstance(String text) throws Exception;
}
- private static final String QUERY = "%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9%2C+%E0%AE%9A%E0%AF%81%E0%AE%B5%E0%AE%BE%E0%AE%B0%E0%AE%B8%E0%AF%8D%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D%2C+%E0%AE%86%E0%AE%A9%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AF%87%E0%AE%B0%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%82%E0%AE%B4%E0%AF%8D%E0%AE%A8%E0%AE%BF%E0%AE%B2%E0%AF%88+%E0%AE%8F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%8E%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%A4%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%AA%E0%AE%A3%E0%AE%BF%E0%AE%AF%E0%AF%88%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%B5%E0%AE%B2%E0%AE%BF+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%88+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%AA%E0%AF%86%E0%AE%B0%E0%AE%BF%E0%AE%AF+%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B2%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%9F%E0%AE%BF%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D.+%E0%AE%85%E0%AE%A4%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%95%E0%AE%B3%E0%AF%88+%E0%AE%AA%E0%AF%86%E0%AE%B1+%E0%AE%A4%E0%AE%B5%E0%AE%BF%E0%AE%B0%2C+%E0%AE%8E%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%89%E0%AE%B4%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%89%E0%AE%9F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%AF%E0%AE%BF%E0%AE%B1%E0%AF%8D%E0%AE%9A%E0%AE%BF+%E0%AE%AE%E0%AF%87%E0%AE%B1%E0%AF%8D%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AE%A4%E0%AF%81+%E0%AE%8E%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81+%E0%AE%87%E0%AE%A4%E0%AF%81+%E0%AE%92%E0%AE%B0%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%BF%E0%AE%AF+%E0%AE%89%E0%AE%A4%E0%AE%BE%E0%AE%B0%E0%AE%A3%E0%AE%AE%E0%AF%8D%2C+%E0%AE%8E%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95.+%E0%AE%B0%E0%AE%AF%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%8E%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%B5%E0%AE%BF%E0%AE%B3%E0%AF%88%E0%AE%B5%E0%AE%BE%E0%AE%95+%E0%AE%87%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%AE%E0%AF%8D+%E0%AE%86%E0%AE%A9%E0%AF%8D%E0%AE%B2%E0%AF%88%E0%AE%A9%E0%AF%8D+%E0%AE%AA%E0%AE%AF%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%BE%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AF%87%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%AF%E0%AE%BE%E0%AE%B0%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%95%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AA%E0%AE%BF%E0%AE%9F%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AE%B0%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D.+%E0%AE%87%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%A8%E0%AE%BF%E0%AE%95%E0%AE%B4%E0%AF%8D%E0%AE%B5%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%86%E0%AE%AF%E0%AF%8D%E0%AE%A4%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%85%E0%AE%AE%E0%AF%88%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%95%E0%AE%A3%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%2C+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%B5%E0%AE%BF%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AF%81+quae+%E0%AE%AA%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AE%B1%E0%AF%88+%E0%AE%A8%E0%AF%80%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%AA%E0%AE%B0%E0%AE%BF%E0%AE%A8%E0%AF%8D%E0%AE%A4%E0%AF%81%E0%AE%B0%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%86%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%AF%E0%AE%BE%E0%AE%95+%E0%AE%AE%E0%AE%BE%E0%AE%B1%E0%AF%81%E0%AE%AE%E0%AF%8D";
+ private static final String QUERY =
+ "%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9%2C+%E0%AE%9A%E0%AF%81%E0%AE%B5%E0%AE%BE%E0%AE%B0%E0%AE%B8%E0%AF%8D%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D%2C+%E0%AE%86%E0%AE%A9%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AF%87%E0%AE%B0%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%82%E0%AE%B4%E0%AF%8D%E0%AE%A8%E0%AE%BF%E0%AE%B2%E0%AF%88+%E0%AE%8F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%8E%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%A4%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%AA%E0%AE%A3%E0%AE%BF%E0%AE%AF%E0%AF%88%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%B5%E0%AE%B2%E0%AE%BF+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%88+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%AA%E0%AF%86%E0%AE%B0%E0%AE%BF%E0%AE%AF+%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B2%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%9F%E0%AE%BF%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D.+%E0%AE%85%E0%AE%A4%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%95%E0%AE%B3%E0%AF%88+%E0%AE%AA%E0%AF%86%E0%AE%B1+%E0%AE%A4%E0%AE%B5%E0%AE%BF%E0%AE%B0%2C+%E0%AE%8E%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%89%E0%AE%B4%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%89%E0%AE%9F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%AF%E0%AE%BF%E0%AE%B1%E0%AF%8D%E0%AE%9A%E0%AE%BF+%E0%AE%AE%E0%AF%87%E0%AE%B1%E0%AF%8D%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AE%A4%E0%AF%81+%E0%AE%8E%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81+%E0%AE%87%E0%AE%A4%E0%AF%81+%E0%AE%92%E0%AE%B0%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%BF%E0%AE%AF+%E0%AE%89%E0%AE%A4%E0%AE%BE%E0%AE%B0%E0%AE%A3%E0%AE%AE%E0%AF%8D%2C+%E0%AE%8E%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95.+%E0%AE%B0%E0%AE%AF%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%8E%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%B5%E0%AE%BF%E0%AE%B3%E0%AF%88%E0%AE%B5%E0%AE%BE%E0%AE%95+%E0%AE%87%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%AE%E0%AF%8D+%E0%AE%86%E0%AE%A9%E0%AF%8D%E0%AE%B2%E0%AF%88%E0%AE%A9%E0%AF%8D+%E0%AE%AA%E0%AE%AF%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%BE%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AF%87%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%AF%E0%AE%BE%E0%AE%B0%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%95%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AA%E0%AE%BF%E0%AE%9F%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AE%B0%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D.+%E0%AE%87%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%A8%E0%AE%BF%E0%AE%95%E0%AE%B4%E0%AF%8D%E0%AE%B5%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%86%E0%AE%AF%E0%AF%8D%E0%AE%A4%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%85%E0%AE%AE%E0%AF%88%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%95%E0%AE%A3%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%2C+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%B5%E0%AE%BF%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AF%81+quae+%E0%AE%AA%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AE%B1%E0%AF%88+%E0%AE%A8%E0%AF%80%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%AA%E0%AE%B0%E0%AE%BF%E0%AE%A8%E0%AF%8D%E0%AE%A4%E0%AF%81%E0%AE%B0%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%86%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%AF%E0%AE%BE%E0%AE%95+%E0%AE%AE%E0%AE%BE%E0%AE%B1%E0%AF%81%E0%AE%AE%E0%AF%8D";
- @Parameterized.Parameters(name = "mType({0})")
- public static Collection cases() {
+ public static Collection getCases() {
final List<Object[]> params = new ArrayList<>();
for (Type type : Type.values()) {
- params.add(new Object[]{type});
+ params.add(new Object[] {type});
}
return params;
}
- @Parameterized.Parameter(0)
- public Type mType;
-
Object mA1;
Object mA2;
Object mB1;
@@ -73,20 +74,13 @@ public final class EqualsHashCodePerfTest {
Object mC1;
Object mC2;
- @Before
- public void setUp() throws Exception {
- mA1 = mType.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
- mA2 = mType.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
- mB1 = mType.newInstance("http://developer.android.com/reference/java/net/URI.html");
- mB2 = mType.newInstance("http://developer.android.com/reference/java/net/URI.html");
-
- mC1 = mType.newInstance("http://developer.android.com/query?q=" + QUERY);
- // Replace the very last char.
- mC2 = mType.newInstance("http://developer.android.com/query?q=" + QUERY.substring(0, QUERY.length() - 3) + "%AF");
- }
-
@Test
- public void timeEquals() {
+ @Parameters(method = "getCases")
+ public void timeEquals(Type type) throws Exception {
+ mA1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+ mA2 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+ mB1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
+ mB2 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mA1.equals(mB1);
@@ -96,7 +90,10 @@ public final class EqualsHashCodePerfTest {
}
@Test
- public void timeHashCode() {
+ @Parameters(method = "getCases")
+ public void timeHashCode(Type type) throws Exception {
+ mA1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+ mB1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mA1.hashCode();
@@ -105,7 +102,15 @@ public final class EqualsHashCodePerfTest {
}
@Test
- public void timeEqualsWithHeavilyEscapedComponent() {
+ @Parameters(method = "getCases")
+ public void timeEqualsWithHeavilyEscapedComponent(Type type) throws Exception {
+ mC1 = type.newInstance("http://developer.android.com/query?q=" + QUERY);
+ // Replace the very last char.
+ mC2 =
+ type.newInstance(
+ "http://developer.android.com/query?q="
+ + QUERY.substring(0, QUERY.length() - 3)
+ + "%AF");
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
mC1.equals(mC2);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
index 6fe9059cb3de..80c448732b59 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
@@ -20,26 +20,24 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class KeyPairGeneratorPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlgorithm={0}, mImplementation={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Algorithm.RSA, Implementation.BouncyCastle},
@@ -48,12 +46,6 @@ public class KeyPairGeneratorPerfTest {
});
}
- @Parameterized.Parameter(0)
- public Algorithm mAlgorithm;
-
- @Parameterized.Parameter(1)
- public Implementation mImplementation;
-
public enum Algorithm {
RSA,
DSA,
@@ -66,26 +58,25 @@ public class KeyPairGeneratorPerfTest {
private String mGeneratorAlgorithm;
private KeyPairGenerator mGenerator;
- private SecureRandom mRandom;
- @Before
- public void setUp() throws Exception {
- this.mGeneratorAlgorithm = mAlgorithm.toString();
+ public void setUp(Algorithm algorithm, Implementation implementation) throws Exception {
+ this.mGeneratorAlgorithm = algorithm.toString();
final String provider;
- if (mImplementation == Implementation.BouncyCastle) {
+ if (implementation == Implementation.BouncyCastle) {
provider = "BC";
} else {
provider = "AndroidOpenSSL";
}
this.mGenerator = KeyPairGenerator.getInstance(mGeneratorAlgorithm, provider);
- this.mRandom = SecureRandom.getInstance("SHA1PRNG");
this.mGenerator.initialize(1024);
}
@Test
- public void time() throws Exception {
+ @Parameters(method = "getData")
+ public void time(Algorithm algorithm, Implementation implementation) throws Exception {
+ setUp(algorithm, implementation);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
KeyPair keyPair = mGenerator.generateKeyPair();
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
index 414764d292b8..c9b0cbe1bedb 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
@@ -20,11 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
@@ -34,36 +35,34 @@ import java.util.Collection;
*
* @author Kevin Bourrillion
*/
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class LoopingBackwardsPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mMax={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{2}, {20}, {2000}, {20000000}});
}
- @Parameterized.Parameter(0)
- public int mMax;
-
@Test
- public void timeForwards() {
+ @Parameters(method = "getData")
+ public void timeForwards(int max) {
int fake = 0;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- for (int j = 0; j < mMax; j++) {
+ for (int j = 0; j < max; j++) {
fake += j;
}
}
}
@Test
- public void timeBackwards() {
+ @Parameters(method = "getData")
+ public void timeBackwards(int max) {
int fake = 0;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- for (int j = mMax - 1; j >= 0; j--) {
+ for (int j = max - 1; j >= 0; j--) {
fake += j;
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
index 279681bc0d15..2dc947a613d2 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
@@ -20,24 +20,24 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class MessageDigestPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlgorithm={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Algorithm.MD5},
@@ -48,9 +48,6 @@ public class MessageDigestPerfTest {
});
}
- @Parameterized.Parameter(0)
- public Algorithm mAlgorithm;
-
public String mProvider = "AndroidOpenSSL";
private static final int DATA_SIZE = 8192;
@@ -97,44 +94,44 @@ public class MessageDigestPerfTest {
};
@Test
- public void time() throws Exception {
+ @Parameters(method = "getData")
+ public void time(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
digest.update(DATA, 0, DATA_SIZE);
digest.digest();
}
}
@Test
- public void timeLargeArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLargeArray(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
digest.update(LARGE_DATA, 0, LARGE_DATA_SIZE);
digest.digest();
}
}
@Test
- public void timeSmallChunkOfLargeArray() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallChunkOfLargeArray(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
digest.update(LARGE_DATA, LARGE_DATA_SIZE / 2, DATA_SIZE);
digest.digest();
}
}
@Test
- public void timeSmallByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
SMALL_BUFFER.position(0);
SMALL_BUFFER.limit(SMALL_BUFFER.capacity());
digest.update(SMALL_BUFFER);
@@ -143,11 +140,11 @@ public class MessageDigestPerfTest {
}
@Test
- public void timeSmallDirectByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallDirectByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
SMALL_DIRECT_BUFFER.position(0);
SMALL_DIRECT_BUFFER.limit(SMALL_DIRECT_BUFFER.capacity());
digest.update(SMALL_DIRECT_BUFFER);
@@ -156,11 +153,11 @@ public class MessageDigestPerfTest {
}
@Test
- public void timeLargeByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLargeByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_BUFFER.position(0);
LARGE_BUFFER.limit(LARGE_BUFFER.capacity());
digest.update(LARGE_BUFFER);
@@ -169,11 +166,11 @@ public class MessageDigestPerfTest {
}
@Test
- public void timeLargeDirectByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeLargeDirectByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_DIRECT_BUFFER.position(0);
LARGE_DIRECT_BUFFER.limit(LARGE_DIRECT_BUFFER.capacity());
digest.update(LARGE_DIRECT_BUFFER);
@@ -182,11 +179,11 @@ public class MessageDigestPerfTest {
}
@Test
- public void timeSmallChunkOfLargeByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallChunkOfLargeByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_BUFFER.position(LARGE_BUFFER.capacity() / 2);
LARGE_BUFFER.limit(LARGE_BUFFER.position() + DATA_SIZE);
digest.update(LARGE_BUFFER);
@@ -195,11 +192,11 @@ public class MessageDigestPerfTest {
}
@Test
- public void timeSmallChunkOfLargeDirectByteBuffer() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSmallChunkOfLargeDirectByteBuffer(Algorithm algorithm) throws Exception {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- MessageDigest digest =
- MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+ MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
LARGE_DIRECT_BUFFER.position(LARGE_DIRECT_BUFFER.capacity() / 2);
LARGE_DIRECT_BUFFER.limit(LARGE_DIRECT_BUFFER.position() + DATA_SIZE);
digest.update(LARGE_DIRECT_BUFFER);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
index 37bd73c8731a..d9d4bb5d0ae1 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
@@ -20,17 +20,18 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class MutableIntPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -96,29 +97,28 @@ public final class MutableIntPerfTest {
abstract int timeGet(BenchmarkState state);
}
- @Parameters(name = "mKind={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{Kind.ARRAY}, {Kind.ATOMIC}});
}
- @Parameterized.Parameter(0)
- public Kind mKind;
-
@Test
- public void timeCreate() {
+ @Parameters(method = "getData")
+ public void timeCreate(Kind kind) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- mKind.timeCreate(state);
+ kind.timeCreate(state);
}
@Test
- public void timeIncrement() {
+ @Parameters(method = "getData")
+ public void timeIncrement(Kind kind) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- mKind.timeIncrement(state);
+ kind.timeIncrement(state);
}
@Test
- public void timeGet() {
+ @Parameters(method = "getData")
+ public void timeGet(Kind kind) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- mKind.timeGet(state);
+ kind.timeGet(state);
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
index 8801a5690cb2..48450b4616e6 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
@@ -20,12 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.ArrayList;
import java.util.Arrays;
@@ -35,13 +35,12 @@ import java.util.List;
import java.util.PriorityQueue;
import java.util.Random;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class PriorityQueuePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mQueueSize={0}, mHitRate={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{100, 0},
@@ -62,26 +61,19 @@ public class PriorityQueuePerfTest {
});
}
- @Parameterized.Parameter(0)
- public int mQueueSize;
-
- @Parameterized.Parameter(1)
- public int mHitRate;
-
private PriorityQueue<Integer> mPq;
private PriorityQueue<Integer> mUsepq;
private List<Integer> mSeekElements;
private Random mRandom = new Random(189279387L);
- @Before
- public void setUp() throws Exception {
+ public void setUp(int queueSize, int hitRate) throws Exception {
mPq = new PriorityQueue<Integer>();
mUsepq = new PriorityQueue<Integer>();
mSeekElements = new ArrayList<Integer>();
List<Integer> allElements = new ArrayList<Integer>();
- int numShared = (int) (mQueueSize * ((double) mHitRate / 100));
- // the total number of elements we require to engineer a hit rate of mHitRate%
- int totalElements = 2 * mQueueSize - numShared;
+ int numShared = (int) (queueSize * ((double) hitRate / 100));
+ // the total number of elements we require to engineer a hit rate of hitRate%
+ int totalElements = 2 * queueSize - numShared;
for (int i = 0; i < totalElements; i++) {
allElements.add(i);
}
@@ -93,11 +85,11 @@ public class PriorityQueuePerfTest {
mSeekElements.add(allElements.get(i));
}
// add priority queue only elements (these won't be touched)
- for (int i = numShared; i < mQueueSize; i++) {
+ for (int i = numShared; i < queueSize; i++) {
mPq.add(allElements.get(i));
}
// add non-priority queue elements (these will be misses)
- for (int i = mQueueSize; i < totalElements; i++) {
+ for (int i = queueSize; i < totalElements; i++) {
mSeekElements.add(allElements.get(i));
}
mUsepq = new PriorityQueue<Integer>(mPq);
@@ -107,16 +99,18 @@ public class PriorityQueuePerfTest {
}
@Test
- public void timeRemove() {
+ @Parameters(method = "getData")
+ public void timeRemove(int queueSize, int hitRate) throws Exception {
+ setUp(queueSize, hitRate);
boolean fake = false;
int elementsSize = mSeekElements.size();
// At most allow the queue to empty 10%.
- int resizingThreshold = mQueueSize / 10;
+ int resizingThreshold = queueSize / 10;
int i = 0;
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
// Reset queue every so often. This will be called more often for smaller
- // mQueueSizes, but since a copy is linear, it will also cost proportionally
+ // queueSizes, but since a copy is linear, it will also cost proportionally
// less, and hopefully it will approximately balance out.
if (++i % resizingThreshold == 0) {
mUsepq = new PriorityQueue<Integer>(mPq);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
index 42dc5811e6db..5ad62dedcae7 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
@@ -20,11 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
@@ -32,7 +33,7 @@ import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class SchemePrefixPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -85,19 +86,16 @@ public final class SchemePrefixPerfTest {
abstract String execute(String spec);
}
- @Parameters(name = "mStrategy={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(new Object[][] {{Strategy.REGEX}, {Strategy.JAVA}});
}
- @Parameterized.Parameter(0)
- public Strategy mStrategy;
-
@Test
- public void timeSchemePrefix() {
+ @Parameters(method = "getData")
+ public void timeSchemePrefix(Strategy strategy) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStrategy.execute("http://android.com");
+ strategy.execute("http://android.com");
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
index 96e7cb27afef..a9a0788f6136 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
@@ -19,12 +19,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -37,13 +37,12 @@ import java.util.HashMap;
import java.util.Map;
/** Tests RSA and DSA mSignature creation and verification. */
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class SignaturePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mAlgorithm={0}, mImplementation={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{Algorithm.MD5WithRSA, Implementation.OpenSSL},
@@ -55,12 +54,6 @@ public class SignaturePerfTest {
});
}
- @Parameterized.Parameter(0)
- public Algorithm mAlgorithm;
-
- @Parameterized.Parameter(1)
- public Implementation mImplementation;
-
private static final int DATA_SIZE = 8192;
private static final byte[] DATA = new byte[DATA_SIZE];
@@ -94,9 +87,8 @@ public class SignaturePerfTest {
private PrivateKey mPrivateKey;
private PublicKey mPublicKey;
- @Before
- public void setUp() throws Exception {
- this.mSignatureAlgorithm = mAlgorithm.toString();
+ public void setUp(Algorithm algorithm) throws Exception {
+ this.mSignatureAlgorithm = algorithm.toString();
String keyAlgorithm =
mSignatureAlgorithm.substring(
@@ -121,11 +113,13 @@ public class SignaturePerfTest {
}
@Test
- public void timeSign() throws Exception {
+ @Parameters(method = "getData")
+ public void timeSign(Algorithm algorithm, Implementation implementation) throws Exception {
+ setUp(algorithm);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Signature signer;
- switch (mImplementation) {
+ switch (implementation) {
case OpenSSL:
signer = Signature.getInstance(mSignatureAlgorithm, "AndroidOpenSSL");
break;
@@ -133,7 +127,7 @@ public class SignaturePerfTest {
signer = Signature.getInstance(mSignatureAlgorithm, "BC");
break;
default:
- throw new RuntimeException(mImplementation.toString());
+ throw new RuntimeException(implementation.toString());
}
signer.initSign(mPrivateKey);
signer.update(DATA);
@@ -142,11 +136,13 @@ public class SignaturePerfTest {
}
@Test
- public void timeVerify() throws Exception {
+ @Parameters(method = "getData")
+ public void timeVerify(Algorithm algorithm, Implementation implementation) throws Exception {
+ setUp(algorithm);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
Signature verifier;
- switch (mImplementation) {
+ switch (implementation) {
case OpenSSL:
verifier = Signature.getInstance(mSignatureAlgorithm, "AndroidOpenSSL");
break;
@@ -154,7 +150,7 @@ public class SignaturePerfTest {
verifier = Signature.getInstance(mSignatureAlgorithm, "BC");
break;
default:
- throw new RuntimeException(mImplementation.toString());
+ throw new RuntimeException(implementation.toString());
}
verifier.initVerify(mPublicKey);
verifier.update(DATA);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
index 02194b1b9745..36db014b75a5 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
@@ -20,16 +20,17 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -46,8 +47,7 @@ public class StringPerfTest {
}
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.EIGHT_KI},
@@ -57,9 +57,6 @@ public class StringPerfTest {
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
private static String makeString(int length) {
StringBuilder result = new StringBuilder(length);
for (int i = 0; i < length; ++i) {
@@ -69,10 +66,11 @@ public class StringPerfTest {
}
@Test
- public void timeHashCode() {
+ @Parameters(method = "getData")
+ public void timeHashCode(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.hashCode();
+ stringLengths.mValue.hashCode();
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
index b0d1ee4132fa..5b4423a32831 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
@@ -20,16 +20,17 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringReplaceAllPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -69,8 +70,7 @@ public class StringReplaceAllPerfTest {
return stringBuilder.toString();
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.BOOT_IMAGE},
@@ -82,30 +82,30 @@ public class StringReplaceAllPerfTest {
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
@Test
- public void timeReplaceAllTrivialPatternNonExistent() {
+ @Parameters(method = "getData")
+ public void timeReplaceAllTrivialPatternNonExistent(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replaceAll("fish", "0");
+ stringLengths.mValue.replaceAll("fish", "0");
}
}
@Test
- public void timeReplaceTrivialPatternAllRepeated() {
+ @Parameters(method = "getData")
+ public void timeReplaceTrivialPatternAllRepeated(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replaceAll("jklm", "0");
+ stringLengths.mValue.replaceAll("jklm", "0");
}
}
@Test
- public void timeReplaceAllTrivialPatternSingleOccurrence() {
+ @Parameters(method = "getData")
+ public void timeReplaceAllTrivialPatternSingleOccurrence(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replaceAll("qrst", "0");
+ stringLengths.mValue.replaceAll("qrst", "0");
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
index d2e657a78608..4d5c792295b9 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
@@ -20,16 +20,17 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringReplacePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -64,8 +65,7 @@ public class StringReplacePerfTest {
return stringBuilder.toString();
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.EMPTY},
@@ -76,54 +76,57 @@ public class StringReplacePerfTest {
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
@Test
- public void timeReplaceCharNonExistent() {
+ @Parameters(method = "getData")
+ public void timeReplaceCharNonExistent(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace('z', '0');
+ stringLengths.mValue.replace('z', '0');
}
}
@Test
- public void timeReplaceCharRepeated() {
+ @Parameters(method = "getData")
+ public void timeReplaceCharRepeated(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace('a', '0');
+ stringLengths.mValue.replace('a', '0');
}
}
@Test
- public void timeReplaceSingleChar() {
+ @Parameters(method = "getData")
+ public void timeReplaceSingleChar(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace('q', '0');
+ stringLengths.mValue.replace('q', '0');
}
}
@Test
- public void timeReplaceSequenceNonExistent() {
+ @Parameters(method = "getData")
+ public void timeReplaceSequenceNonExistent(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace("fish", "0");
+ stringLengths.mValue.replace("fish", "0");
}
}
@Test
- public void timeReplaceSequenceRepeated() {
+ @Parameters(method = "getData")
+ public void timeReplaceSequenceRepeated(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace("jklm", "0");
+ stringLengths.mValue.replace("jklm", "0");
}
}
@Test
- public void timeReplaceSingleSequence() {
+ @Parameters(method = "getData")
+ public void timeReplaceSingleSequence(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.replace("qrst", "0");
+ stringLengths.mValue.replace("qrst", "0");
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
index 1efc188f4e4d..c004d959b9b3 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
@@ -20,17 +20,18 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringToBytesPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -53,8 +54,7 @@ public class StringToBytesPerfTest {
}
}
- @Parameters(name = "mStringLengths={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{StringLengths.EMPTY},
@@ -69,9 +69,6 @@ public class StringToBytesPerfTest {
});
}
- @Parameterized.Parameter(0)
- public StringLengths mStringLengths;
-
private static String makeString(int length) {
char[] chars = new char[length];
for (int i = 0; i < length; ++i) {
@@ -89,26 +86,29 @@ public class StringToBytesPerfTest {
}
@Test
- public void timeGetBytesUtf8() {
+ @Parameters(method = "getData")
+ public void timeGetBytesUtf8(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.getBytes(StandardCharsets.UTF_8);
+ stringLengths.mValue.getBytes(StandardCharsets.UTF_8);
}
}
@Test
- public void timeGetBytesIso88591() {
+ @Parameters(method = "getData")
+ public void timeGetBytesIso88591(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.getBytes(StandardCharsets.ISO_8859_1);
+ stringLengths.mValue.getBytes(StandardCharsets.ISO_8859_1);
}
}
@Test
- public void timeGetBytesAscii() {
+ @Parameters(method = "getData")
+ public void timeGetBytesAscii(StringLengths stringLengths) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- mStringLengths.mValue.getBytes(StandardCharsets.US_ASCII);
+ stringLengths.mValue.getBytes(StandardCharsets.US_ASCII);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
index b01948aa2255..15516fc1c51c 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
@@ -20,22 +20,22 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import java.util.Arrays;
import java.util.Collection;
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public class StringToRealPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mString={0}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{"NaN"},
@@ -49,22 +49,21 @@ public class StringToRealPerfTest {
});
}
- @Parameterized.Parameter(0)
- public String mString;
-
@Test
- public void timeFloat_parseFloat() {
+ @Parameters(method = "getData")
+ public void timeFloat_parseFloat(String string) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- Float.parseFloat(mString);
+ Float.parseFloat(string);
}
}
@Test
- public void timeDouble_parseDouble() {
+ @Parameters(method = "getData")
+ public void timeDouble_parseDouble(String string) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
- Double.parseDouble(mString);
+ Double.parseDouble(string);
}
}
}
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
index 2ea834d0b71c..ae1e8bce42ac 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
@@ -20,12 +20,12 @@ import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.test.suitebuilder.annotation.LargeTest;
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.xml.sax.InputSource;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
@@ -38,13 +38,12 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
// http://code.google.com/p/android/issues/detail?id=18102
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class XMLEntitiesPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameters(name = "mLength={0}, mEntityFraction={1}")
- public static Collection<Object[]> data() {
+ public static Collection<Object[]> getData() {
return Arrays.asList(
new Object[][] {
{10, 0},
@@ -59,29 +58,22 @@ public final class XMLEntitiesPerfTest {
});
}
- @Parameterized.Parameter(0)
- public int mLength;
-
- @Parameterized.Parameter(1)
- public float mEntityFraction;
-
private XmlPullParserFactory mXmlPullParserFactory;
private DocumentBuilderFactory mDocumentBuilderFactory;
/** a string like {@code <doc>&amp;&amp;++</doc>}. */
private String mXml;
- @Before
- public void setUp() throws Exception {
+ public void setUp(int length, float entityFraction) throws Exception {
mXmlPullParserFactory = XmlPullParserFactory.newInstance();
mDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
StringBuilder xmlBuilder = new StringBuilder();
xmlBuilder.append("<doc>");
- for (int i = 0; i < (mLength * mEntityFraction); i++) {
+ for (int i = 0; i < (length * entityFraction); i++) {
xmlBuilder.append("&amp;");
}
- while (xmlBuilder.length() < mLength) {
+ while (xmlBuilder.length() < length) {
xmlBuilder.append("+");
}
xmlBuilder.append("</doc>");
@@ -89,7 +81,9 @@ public final class XMLEntitiesPerfTest {
}
@Test
- public void timeXmlParser() throws Exception {
+ @Parameters(method = "getData")
+ public void timeXmlParser(int length, float entityFraction) throws Exception {
+ setUp(length, entityFraction);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
XmlPullParser parser = mXmlPullParserFactory.newPullParser();
@@ -101,7 +95,9 @@ public final class XMLEntitiesPerfTest {
}
@Test
- public void timeDocumentBuilder() throws Exception {
+ @Parameters(method = "getData")
+ public void timeDocumentBuilder(int length, float entityFraction) throws Exception {
+ setUp(length, entityFraction);
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
DocumentBuilder documentBuilder = mDocumentBuilderFactory.newDocumentBuilder();
diff --git a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
index b43ec10e1fc5..a31184ce03c9 100644
--- a/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
+++ b/apct-tests/perftests/core/src/com/android/internal/util/FastDataPerfTest.java
@@ -22,6 +22,9 @@ import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.FastDataInput;
+import com.android.modules.utils.FastDataOutput;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index f844ba3bb0a9..cc74a5294f9d 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -30,8 +30,8 @@ import android.view.IWindowSession;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -84,7 +84,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
private static class TestWindow extends BaseIWindow {
final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
- final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ final int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
final Rect mOutAttachedFrame = new Rect();
@@ -106,7 +106,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
- Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel,
+ Display.DEFAULT_DISPLAY, mRequestedVisibleTypes, inputChannel,
mOutInsetsState, mOutControls, mOutAttachedFrame, mOutSizeCompatScale);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/core/api/current.txt b/core/api/current.txt
index 18d8fc72a40d..218d7bd14ceb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -172,6 +172,7 @@ package android {
field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+ field public static final String RUN_LONG_JOBS = "android.permission.RUN_LONG_JOBS";
field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
field public static final String SEND_SMS = "android.permission.SEND_SMS";
@@ -9268,6 +9269,7 @@ package android.content {
field public static final int CLASSIFICATION_NOT_COMPLETE = 1; // 0x1
field public static final int CLASSIFICATION_NOT_PERFORMED = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
+ field public static final String EXTRA_IS_REMOTE_DEVICE = "android.content.extra.IS_REMOTE_DEVICE";
field public static final String EXTRA_IS_SENSITIVE = "android.content.extra.IS_SENSITIVE";
field public static final String MIMETYPE_TEXT_HTML = "text/html";
field public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
@@ -17593,6 +17595,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.Capability[]> CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_CAPABILITIES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_SCENE_MODES;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_SETTINGS_OVERRIDES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AWB_AVAILABLE_MODES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AWB_LOCK_AVAILABLE;
@@ -17947,6 +17950,8 @@ package android.hardware.camera2 {
field public static final int CONTROL_SCENE_MODE_STEADYPHOTO = 11; // 0xb
field public static final int CONTROL_SCENE_MODE_SUNSET = 10; // 0xa
field public static final int CONTROL_SCENE_MODE_THEATRE = 7; // 0x7
+ field public static final int CONTROL_SETTINGS_OVERRIDE_OFF = 0; // 0x0
+ field public static final int CONTROL_SETTINGS_OVERRIDE_ZOOM = 1; // 0x1
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_OFF = 0; // 0x0
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_ON = 1; // 0x1
field public static final int CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION = 2; // 0x2
@@ -18145,6 +18150,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SCENE_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_SETTINGS_OVERRIDE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
@@ -18235,6 +18241,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SETTINGS_OVERRIDE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_VIDEO_STABILIZATION_MODE;
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;
@@ -19491,7 +19498,7 @@ package android.location {
method public boolean hasSatelliteBlocklist();
method public boolean hasSatellitePvt();
method public boolean hasScheduling();
- method public boolean hasSingleShot();
+ method public boolean hasSingleShotFix();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssCapabilities> CREATOR;
}
@@ -19523,7 +19530,7 @@ package android.location {
method @NonNull public android.location.GnssCapabilities.Builder setHasSatelliteBlocklist(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasSatellitePvt(boolean);
method @NonNull public android.location.GnssCapabilities.Builder setHasScheduling(boolean);
- method @NonNull public android.location.GnssCapabilities.Builder setHasSingleShot(boolean);
+ method @NonNull public android.location.GnssCapabilities.Builder setHasSingleShotFix(boolean);
}
public final class GnssClock implements android.os.Parcelable {
@@ -19721,7 +19728,7 @@ package android.location {
method public int getConstellationType(@IntRange(from=0) int);
method @FloatRange(from=0xffffffa6, to=90) public float getElevationDegrees(@IntRange(from=0) int);
method @IntRange(from=0) public int getSatelliteCount();
- method @IntRange(from=1, to=200) public int getSvid(@IntRange(from=0) int);
+ method @IntRange(from=1, to=206) public int getSvid(@IntRange(from=0) int);
method public boolean hasAlmanacData(@IntRange(from=0) int);
method public boolean hasBasebandCn0DbHz(@IntRange(from=0) int);
method public boolean hasCarrierFrequencyHz(@IntRange(from=0) int);
@@ -44000,9 +44007,10 @@ package android.telephony {
field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L
field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L
field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L
+ field public static final long NETWORK_TYPE_BITMASK_IDEN = 1024L; // 0x400L
field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
- field public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
+ field @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
@@ -47425,6 +47433,7 @@ package android.util {
field public static final int DENSITY_420 = 420; // 0x1a4
field public static final int DENSITY_440 = 440; // 0x1b8
field public static final int DENSITY_450 = 450; // 0x1c2
+ field public static final int DENSITY_520 = 520; // 0x208
field public static final int DENSITY_560 = 560; // 0x230
field public static final int DENSITY_600 = 600; // 0x258
field public static final int DENSITY_DEFAULT = 160; // 0xa0
@@ -49939,10 +49948,13 @@ package android.view {
method public void clear();
method public void computeCurrentVelocity(int);
method public void computeCurrentVelocity(int, float);
+ method public float getAxisVelocity(int, int);
+ method public float getAxisVelocity(int);
method public float getXVelocity();
method public float getXVelocity(int);
method public float getYVelocity();
method public float getYVelocity(int);
+ method public boolean isAxisSupported(int);
method public static android.view.VelocityTracker obtain();
method public void recycle();
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e890005c0479..88efcced78fb 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -301,10 +301,6 @@ package android.os {
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public void reportNetworkInterfaceForTransports(@NonNull String, @NonNull int[]) throws java.lang.RuntimeException;
}
- public class Binder implements android.os.IBinder {
- method public final void markVintfStability();
- }
-
public class BluetoothServiceManager {
method @NonNull public android.os.BluetoothServiceManager.ServiceRegisterer getBluetoothManagerServiceRegisterer();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7a22e373045d..a382ecfc99d3 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -775,11 +775,14 @@ package android.app {
}
public class BroadcastOptions {
+ method public void clearDeliveryGroupPolicy();
method public void clearRequireCompatChange();
+ method public int getDeliveryGroupPolicy();
method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public static android.app.BroadcastOptions makeBasic();
method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
+ method public void setDeliveryGroupPolicy(int);
method public void setDontSendToRestrictedApps(boolean);
method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
method public void setRequireAllOfPermissions(@Nullable String[]);
@@ -788,6 +791,8 @@ package android.app {
method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
method public android.os.Bundle toBundle();
+ field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
+ field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
}
public class DownloadManager {
@@ -9346,6 +9351,7 @@ package android.os {
public class Binder implements android.os.IBinder {
method public int handleShellCommand(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull android.os.ParcelFileDescriptor, @NonNull String[]);
+ method public final void markVintfStability();
method public static void setProxyTransactListener(@Nullable android.os.Binder.ProxyTransactListener);
}
@@ -13392,7 +13398,7 @@ package android.telephony {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int[] getCompleteActiveSubscriptionIdList();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
- method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public android.os.UserHandle getUserHandle(int);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public android.os.UserHandle getSubscriptionUserHandle(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSubscriptionEnabled(int);
method public void requestEmbeddedSubscriptionInfoListRefresh();
method public void requestEmbeddedSubscriptionInfoListRefresh(int);
@@ -13402,8 +13408,8 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDefaultVoiceSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPreferredDataSubscriptionId(int, boolean, @Nullable java.util.concurrent.Executor, @Nullable java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setSubscriptionEnabled(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public void setSubscriptionUserHandle(int, @Nullable android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUiccApplicationsEnabled(int, boolean);
- method @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION) public void setUserHandle(int, @Nullable android.os.UserHandle);
field @RequiresPermission(android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS) public static final String ACTION_SUBSCRIPTION_PLANS_CHANGED = "android.telephony.action.SUBSCRIPTION_PLANS_CHANGED";
field @NonNull public static final android.net.Uri ADVANCED_CALLING_ENABLED_CONTENT_URI;
field @NonNull public static final android.net.Uri CROSS_SIM_ENABLED_CONTENT_URI;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 3f39026c8189..1b972e0cb81a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -867,7 +867,7 @@ public class AppOpsManager {
// when adding one of these:
// - increment _NUM_OP
- // - define an OPSTR_* constant (marked as @SystemApi)
+ // - define an OPSTR_* constant (and mark as @SystemApi if needed)
// - add row to sAppOpInfos
// - add descriptive strings to Settings/res/values/arrays.xml
// - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
@@ -1353,9 +1353,16 @@ public class AppOpsManager {
public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
+ /**
+ * App can schedule long running jobs.
+ *
+ * @hide
+ */
+ public static final int OP_RUN_LONG_JOBS = AppProtoEnums.APP_OP_RUN_LONG_JOBS;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 122;
+ public static final int _NUM_OP = 123;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1839,6 +1846,13 @@ public class AppOpsManager {
public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
"android:receive_explicit_user_interaction_audio";
+ /**
+ * App can schedule long running jobs.
+ *
+ * @hide
+ */
+ public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1933,6 +1947,7 @@ public class AppOpsManager {
OP_SCHEDULE_EXACT_ALARM,
OP_MANAGE_MEDIA,
OP_TURN_SCREEN_ON,
+ OP_RUN_LONG_JOBS,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2312,7 +2327,9 @@ public class AppOpsManager {
new AppOpInfo.Builder(OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
"RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode(
- AppOpsManager.MODE_ALLOWED).build()
+ AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RUN_LONG_JOBS, OPSTR_RUN_LONG_JOBS, "RUN_LONG_JOBS")
+ .setPermission(Manifest.permission.RUN_LONG_JOBS).build()
};
/**
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index cc4650a7df71..48638d1fdff4 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -31,6 +31,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
@@ -67,6 +68,7 @@ public class BroadcastOptions extends ComponentOptions {
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
private @Nullable String mDeliveryGroupKey;
+ private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
/**
* Change ID which is invalid.
@@ -218,6 +220,12 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.deliveryGroupKey";
/**
+ * Corresponds to {@link #setDeliveryGroupExtrasMerger(BundleMerger)}.
+ */
+ private static final String KEY_DELIVERY_GROUP_EXTRAS_MERGER =
+ "android:broadcast.deliveryGroupExtrasMerger";
+
+ /**
* The list of delivery group policies which specify how multiple broadcasts belonging to
* the same delivery group has to be handled.
* @hide
@@ -225,6 +233,7 @@ public class BroadcastOptions extends ComponentOptions {
@IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
DELIVERY_GROUP_POLICY_ALL,
DELIVERY_GROUP_POLICY_MOST_RECENT,
+ DELIVERY_GROUP_POLICY_MERGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeliveryGroupPolicy {}
@@ -235,6 +244,7 @@ public class BroadcastOptions extends ComponentOptions {
*
* @hide
*/
+ @SystemApi
public static final int DELIVERY_GROUP_POLICY_ALL = 0;
/**
@@ -243,8 +253,17 @@ public class BroadcastOptions extends ComponentOptions {
*
* @hide
*/
+ @SystemApi
public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
+ /**
+ * Delivery group policy that indicates that the extras data from the broadcasts in the
+ * delivery group need to be merged into a single broadcast and the rest can be dropped.
+ *
+ * @hide
+ */
+ public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
+
public static BroadcastOptions makeBasic() {
BroadcastOptions opts = new BroadcastOptions();
return opts;
@@ -295,6 +314,8 @@ public class BroadcastOptions extends ComponentOptions {
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
mDeliveryGroupKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+ mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
+ BundleMerger.class);
}
/**
@@ -724,16 +745,35 @@ public class BroadcastOptions extends ComponentOptions {
*
* @hide
*/
+ @SystemApi
public void setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
mDeliveryGroupPolicy = policy;
}
- /** @hide */
+ /**
+ * Get the delivery group policy for this broadcast that specifies how multiple broadcasts
+ * belonging to the same delivery group has to be handled.
+ *
+ * @hide
+ */
+ @SystemApi
public @DeliveryGroupPolicy int getDeliveryGroupPolicy() {
return mDeliveryGroupPolicy;
}
/**
+ * Clears any previously set delivery group policies using
+ * {@link #setDeliveryGroupKey(String, String)} and resets the delivery group policy to
+ * the default value ({@link #DELIVERY_GROUP_POLICY_ALL}).
+ *
+ * @hide
+ */
+ @SystemApi
+ public void clearDeliveryGroupPolicy() {
+ mDeliveryGroupPolicy = DELIVERY_GROUP_POLICY_ALL;
+ }
+
+ /**
* Set namespace and key to identify the delivery group that this broadcast belongs to.
* If no namespace and key is set, then by default {@link Intent#filterEquals(Intent)} will be
* used to identify the delivery group.
@@ -754,12 +794,35 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
+ * Set the {@link BundleMerger} that specifies how to merge the extras data from
+ * broadcasts in a delivery group.
+ *
+ * <p>Note that this value will be ignored if the delivery group policy is not set as
+ * {@link #DELIVERY_GROUP_POLICY_MERGED}.
+ *
+ * @hide
+ */
+ public void setDeliveryGroupExtrasMerger(@NonNull BundleMerger extrasMerger) {
+ Preconditions.checkNotNull(extrasMerger);
+ mDeliveryGroupExtrasMerger = extrasMerger;
+ }
+
+ /** @hide */
+ public @Nullable BundleMerger getDeliveryGroupExtrasMerger() {
+ return mDeliveryGroupExtrasMerger;
+ }
+
+ /**
* Returns the created options as a Bundle, which can be passed to
* {@link android.content.Context#sendBroadcast(android.content.Intent)
* Context.sendBroadcast(Intent)} and related methods.
* Note that the returned Bundle is still owned by the BroadcastOptions
* object; you must not modify it, but can supply it to the sendBroadcast
* methods that take an options Bundle.
+ *
+ * @throws IllegalStateException if the broadcast option values are inconsistent. For example,
+ * if the delivery group policy is specified as "MERGED" but no
+ * extras merger is supplied.
*/
@Override
public Bundle toBundle() {
@@ -810,6 +873,15 @@ public class BroadcastOptions extends ComponentOptions {
if (mDeliveryGroupKey != null) {
b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupKey);
}
+ if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
+ if (mDeliveryGroupExtrasMerger != null) {
+ b.putParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
+ mDeliveryGroupExtrasMerger);
+ } else {
+ throw new IllegalStateException("Extras merger cannot be empty "
+ + "when delivery group policy is 'MERGED'");
+ }
+ }
return b.isEmpty() ? null : b;
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 08a6b8c4e135..aaa3d21a0b25 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -173,6 +173,7 @@ import android.os.IThermalService;
import android.os.IUserManager;
import android.os.IncidentManager;
import android.os.PerformanceHintManager;
+import android.os.PermissionEnforcer;
import android.os.PowerManager;
import android.os.RecoverySystem;
import android.os.ServiceManager;
@@ -1366,6 +1367,14 @@ public final class SystemServiceRegistry {
return new PermissionCheckerManager(ctx.getOuterContext());
}});
+ registerService(Context.PERMISSION_ENFORCER_SERVICE, PermissionEnforcer.class,
+ new CachedServiceFetcher<PermissionEnforcer>() {
+ @Override
+ public PermissionEnforcer createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new PermissionEnforcer(ctx.getOuterContext());
+ }});
+
registerService(Context.DYNAMIC_SYSTEM_SERVICE, DynamicSystemManager.class,
new CachedServiceFetcher<DynamicSystemManager>() {
@Override
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 5b0bd9614f83..0f26818ead71 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -175,6 +175,23 @@
"file_patterns": [
"(/|^)KeyguardManager.java"
]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.app.PropertyInvalidatedCacheTests"
+ }
+ ],
+ "file_patterns": [
+ "(/|^)PropertyInvalidatedCache.java"
+ ]
}
],
"presubmit-large": [
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index b789b38c966e..760c6f0fc333 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -19,10 +19,15 @@ package android.app.backup;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -38,6 +43,8 @@ import java.util.Map;
* @hide
*/
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.
@@ -72,6 +79,8 @@ public class BackupRestoreEventLogger {
public @interface BackupRestoreError {}
private final int mOperationType;
+ private final Map<String, DataTypeResult> mResults = new HashMap<>();
+ private final MessageDigest mHashDigest;
/**
* @param operationType type of the operation for which logging will be performed. See
@@ -81,6 +90,14 @@ public class BackupRestoreEventLogger {
*/
public BackupRestoreEventLogger(@OperationType int operationType) {
mOperationType = operationType;
+
+ MessageDigest hashDigest = null;
+ try {
+ hashDigest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ Slog.w("Couldn't create MessageDigest for hash computation", e);
+ }
+ mHashDigest = hashDigest;
}
/**
@@ -98,7 +115,7 @@ public class BackupRestoreEventLogger {
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
- return true;
+ return logSuccess(OperationType.BACKUP, dataType, count);
}
/**
@@ -118,7 +135,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return true;
+ return logFailure(OperationType.BACKUP, dataType, count, error);
}
/**
@@ -139,7 +156,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metaData) {
- return true;
+ return logMetaData(OperationType.BACKUP, dataType, metaData);
}
/**
@@ -159,7 +176,7 @@ public class BackupRestoreEventLogger {
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
- return true;
+ return logSuccess(OperationType.RESTORE, dataType, count);
}
/**
@@ -181,7 +198,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
- return true;
+ return logFailure(OperationType.RESTORE, dataType, count, error);
}
/**
@@ -204,7 +221,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metadata) {
- return true;
+ return logMetaData(OperationType.RESTORE, dataType, metadata);
}
/**
@@ -214,7 +231,7 @@ public class BackupRestoreEventLogger {
* @hide
*/
public List<DataTypeResult> getLoggingResults() {
- return Collections.emptyList();
+ return new ArrayList<>(mResults.values());
}
/**
@@ -227,22 +244,97 @@ public class BackupRestoreEventLogger {
return mOperationType;
}
+ private boolean logSuccess(@OperationType int operationType,
+ @BackupRestoreDataType String dataType, int count) {
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mSuccessCount += count;
+ mResults.put(dataType, dataTypeResult);
+
+ return true;
+ }
+
+ private boolean logFailure(@OperationType int operationType,
+ @NonNull @BackupRestoreDataType String dataType, int count,
+ @Nullable @BackupRestoreError String error) {
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mFailCount += count;
+ if (error != null) {
+ dataTypeResult.mErrors.merge(error, count, Integer::sum);
+ }
+
+ return true;
+ }
+
+ private boolean logMetaData(@OperationType int operationType,
+ @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
+ if (mHashDigest == null) {
+ return false;
+ }
+ DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+ if (dataTypeResult == null) {
+ return false;
+ }
+
+ dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
+
+ return true;
+ }
+
+ /**
+ * Get the result container for the given data type.
+ *
+ * @return {@code DataTypeResult} object corresponding to the given {@code dataType} or
+ * {@code null} if the logger can't accept logs for the given data type.
+ */
+ @Nullable
+ private DataTypeResult getDataTypeResult(@OperationType int operationType,
+ @BackupRestoreDataType String dataType) {
+ if (operationType != mOperationType) {
+ // Operation type for which we're trying to record logs doesn't match the operation
+ // type for which this logger instance was created.
+ Slog.d(TAG, "Operation type mismatch: logger created for " + mOperationType
+ + ", trying to log for " + operationType);
+ return null;
+ }
+
+ if (!mResults.containsKey(dataType)) {
+ if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+ // This is a new data type and we're already at capacity.
+ Slog.d(TAG, "Logger is full, ignoring new data type");
+ return null;
+ }
+
+ mResults.put(dataType, new DataTypeResult(dataType));
+ }
+
+ return mResults.get(dataType);
+ }
+
+ private byte[] getMetaDataHash(String metaData) {
+ return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+ }
+
/**
* Encapsulate logging results for a single data type.
*/
public static class DataTypeResult {
@BackupRestoreDataType
private final String mDataType;
- private final int mSuccessCount;
- private final Map<String, Integer> mErrors;
- private final byte[] mMetadataHash;
+ private int mSuccessCount;
+ private int mFailCount;
+ private final Map<String, Integer> mErrors = new HashMap<>();
+ private byte[] mMetadataHash;
- public DataTypeResult(String dataType, int successCount,
- Map<String, Integer> errors, byte[] metadataHash) {
+ public DataTypeResult(String dataType) {
mDataType = dataType;
- mSuccessCount = successCount;
- mErrors = errors;
- mMetadataHash = metadataHash;
}
@NonNull
@@ -260,6 +352,13 @@ public class BackupRestoreEventLogger {
}
/**
+ * @return number of items of the given data type that have failed to back up or restore.
+ */
+ public int getFailCount() {
+ return mFailCount;
+ }
+
+ /**
* @return mapping of {@link BackupRestoreError} to the count of items that are affected by
* the error.
*/
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index bf466116009b..de2ba44ca393 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -139,21 +139,28 @@ public class ClipDescription implements Parcelable {
* password or credit card number.
* <p>
* Type: boolean
- * </p>
* <p>
* This extra can be used to indicate that a ClipData contains sensitive information that
* should be redacted or hidden from view until a user takes explicit action to reveal it
* (e.g., by pasting).
- * </p>
* <p>
* Adding this extra does not change clipboard behavior or add additional security to
* the ClipData. Its purpose is essentially a rendering hint from the source application,
* asking that the data within be obfuscated or redacted, unless the user has taken action
* to make it visible.
- * </p>
*/
public static final String EXTRA_IS_SENSITIVE = "android.content.extra.IS_SENSITIVE";
+ /** Indicates that a ClipData's source is a remote device.
+ * <p>
+ * Type: boolean
+ * <p>
+ * This extra can be used to indicate that a ClipData comes from a separate device rather
+ * than being local. It is a rendering hint that can be used to take different behavior
+ * based on the source device of copied data.
+ */
+ public static final String EXTRA_IS_REMOTE_DEVICE = "android.content.extra.IS_REMOTE_DEVICE";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(value =
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 5f859846a5c1..f12e971afb1f 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -314,17 +314,14 @@ public final class ComponentName implements Parcelable, Cloneable, Comparable<Co
*/
@Override
public boolean equals(@Nullable Object obj) {
- try {
- if (obj != null) {
- ComponentName other = (ComponentName)obj;
- // Note: no null checks, because mPackage and mClass can
- // never be null.
- return mPackage.equals(other.mPackage)
- && mClass.equals(other.mClass);
- }
- } catch (ClassCastException e) {
+ if (obj instanceof ComponentName) {
+ ComponentName other = (ComponentName) obj;
+ // mPackage and mClass can never be null.
+ return mPackage.equals(other.mPackage)
+ && mClass.equals(other.mClass);
+ } else {
+ return false;
}
- return false;
}
@Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d65210b8a0bc..1df0fa8084a5 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5142,6 +5142,14 @@ public abstract class Context {
public static final String PERMISSION_CHECKER_SERVICE = "permission_checker";
/**
+ * Official published name of the (internal) permission enforcer service.
+ *
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String PERMISSION_ENFORCER_SERVICE = "permission_enforcer";
+
+ /**
* Use with {@link #getSystemService(String) to retrieve an
* {@link android.apphibernation.AppHibernationManager}} for
* communicating with the hibernation service.
@@ -5194,6 +5202,15 @@ public abstract class Context {
public static final String DROPBOX_SERVICE = "dropbox";
/**
+ * System service name for BackgroundInstallControlService. This service supervises the MBAs
+ * on device and provides the related metadata of the MBAs.
+ *
+ * @hide
+ */
+ @SuppressLint("ServiceName")
+ public static final String BACKGROUND_INSTALL_CONTROL_SERVICE = "background_install_control";
+
+ /**
* System service name for BinaryTransparencyService. This is used to retrieve measurements
* pertaining to various pre-installed and system binaries on device for the purposes of
* providing transparency to the user.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 43fa61782bf6..f2ebec6306f8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -49,6 +49,7 @@ import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.IBinder;
import android.os.IncidentManager;
import android.os.Parcel;
@@ -11072,6 +11073,20 @@ public class Intent implements Parcelable, Cloneable {
}
/**
+ * Merge the extras data in this intent with that of other supplied intent using the
+ * strategy specified using {@code extrasMerger}.
+ *
+ * <p> Note the extras data in this intent is treated as the {@code first} param
+ * and the extras data in {@code other} intent is treated as the {@code last} param
+ * when using the passed in {@link BundleMerger} object.
+ *
+ * @hide
+ */
+ public void mergeExtras(@NonNull Intent other, @NonNull BundleMerger extrasMerger) {
+ mExtras = extrasMerger.merge(mExtras, other.mExtras);
+ }
+
+ /**
* Wrapper class holding an Intent and implementing comparisons on it for
* the purpose of filtering. The class implements its
* {@link #equals equals()} and {@link #hashCode hashCode()} methods as
diff --git a/core/jni/include_vm/android_runtime/vm.h b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
index a6e7c162d6ed..c8e7caebc821 100644
--- a/core/jni/include_vm/android_runtime/vm.h
+++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
@@ -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,11 +14,13 @@
* limitations under the License.
*/
-#pragma once
+package android.content.pm;
-#include <jni.h>
+import android.content.pm.ParceledListSlice;
-// Get the Java VM. If the symbol doesn't exist at runtime, it means libandroid_runtime
-// is not loaded in the current process. If the symbol exists but it returns nullptr, it
-// means JavaVM is not yet started.
-extern "C" JavaVM* AndroidRuntimeGetJavaVM();
+/**
+ * {@hide}
+ */
+interface IBackgroundInstallControlService {
+ ParceledListSlice getBackgroundInstalledPackages(long flags, int userId);
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index f63472680bb0..d3cb59dbb034 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1286,6 +1286,30 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<android.hardware.camera2.params.HighSpeedVideoConfiguration[]>("android.control.availableHighSpeedVideoConfigurationsMaximumResolution", android.hardware.camera2.params.HighSpeedVideoConfiguration[].class);
/**
+ * <p>List of available settings overrides supported by the camera device that can
+ * be used to speed up certain controls.</p>
+ * <p>When not all controls within a CaptureRequest are required to take effect
+ * at the same time on the outputs, the camera device may apply certain request keys sooner
+ * to improve latency. This list contains such supported settings overrides. Each settings
+ * override corresponds to a set of CaptureRequest keys that can be sped up when applying.</p>
+ * <p>A supported settings override can be passed in via
+ * {@link android.hardware.camera2.CaptureRequest#CONTROL_SETTINGS_OVERRIDE }, and the
+ * CaptureRequest keys corresponding to the override are applied as soon as possible, not
+ * bound by per-frame synchronization. See {@link CaptureRequest#CONTROL_SETTINGS_OVERRIDE android.control.settingsOverride} for the
+ * CaptureRequest keys for each override.</p>
+ * <p>OFF is always included in this list.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#CONTROL_SETTINGS_OVERRIDE android.control.settingsOverride}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<int[]> CONTROL_AVAILABLE_SETTINGS_OVERRIDES =
+ new Key<int[]>("android.control.availableSettingsOverrides", int[].class);
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 1e1d44368713..545aa8f2e80d 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3201,6 +3201,49 @@ public abstract class CameraMetadata<TKey> {
public static final int CONTROL_EXTENDED_SCENE_MODE_VENDOR_START = 0x40;
//
+ // Enumeration values for CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ //
+
+ /**
+ * <p>No keys are applied sooner than the other keys when applying CaptureRequest
+ * settings to the camera device. This is the default value.</p>
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ */
+ public static final int CONTROL_SETTINGS_OVERRIDE_OFF = 0;
+
+ /**
+ * <p>Zoom related keys are applied sooner than the other keys in the CaptureRequest. The
+ * zoom related keys are:</p>
+ * <ul>
+ * <li>{@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}</li>
+ * <li>{@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}</li>
+ * <li>{@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}</li>
+ * <li>{@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions}</li>
+ * <li>{@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions}</li>
+ * </ul>
+ * <p>Even though {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions}, {@link CaptureRequest#CONTROL_AWB_REGIONS android.control.awbRegions},
+ * and {@link CaptureRequest#CONTROL_AF_REGIONS android.control.afRegions} are not directly zoom related, applications
+ * typically scale these regions together with {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} to have a
+ * consistent mapping within the current field of view. In this aspect, they are
+ * related to {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_REGIONS
+ * @see CaptureRequest#CONTROL_AF_REGIONS
+ * @see CaptureRequest#CONTROL_AWB_REGIONS
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ */
+ public static final int CONTROL_SETTINGS_OVERRIDE_ZOOM = 1;
+
+ /**
+ * <p>Vendor defined settingsOverride. These depend on vendor implementation.</p>
+ * @see CaptureRequest#CONTROL_SETTINGS_OVERRIDE
+ * @hide
+ */
+ public static final int CONTROL_SETTINGS_OVERRIDE_VENDOR_START = 0x4000;
+
+ //
// Enumeration values for CaptureRequest#EDGE_MODE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c5cf0f695040..407ea07838de 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2429,6 +2429,90 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
new Key<Boolean>("android.control.awbRegionsSet", boolean.class);
/**
+ * <p>The desired CaptureRequest settings override with which certain keys are
+ * applied earlier so that they can take effect sooner.</p>
+ * <p>There are some CaptureRequest keys which can be applied earlier than others
+ * when controls within a CaptureRequest aren't required to take effect at the same time.
+ * One such example is zoom. Zoom can be applied at a later stage of the camera pipeline.
+ * As soon as the camera device receives the CaptureRequest, it can apply the requested
+ * zoom value onto an earlier request that's already in the pipeline, thus improves zoom
+ * latency.</p>
+ * <p>This key's value in the capture result reflects whether the controls for this capture
+ * are overridden "by" a newer request. This means that if a capture request turns on
+ * settings override, the capture result of an earlier request will contain the key value
+ * of ZOOM. On the other hand, if a capture request has settings override turned on,
+ * but all newer requests have it turned off, the key's value in the capture result will
+ * be OFF because this capture isn't overridden by a newer capture. In the two examples
+ * below, the capture results columns illustrate the settingsOverride values in different
+ * scenarios.</p>
+ * <p>Assuming the zoom settings override can speed up by 1 frame, below example illustrates
+ * the speed-up at the start of capture session:</p>
+ * <pre><code>Camera session created
+ * Request 1 (zoom=1.0x, override=ZOOM) -&gt;
+ * Request 2 (zoom=1.2x, override=ZOOM) -&gt;
+ * Request 3 (zoom=1.4x, override=ZOOM) -&gt; Result 1 (zoom=1.2x, override=ZOOM)
+ * Request 4 (zoom=1.6x, override=ZOOM) -&gt; Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=ZOOM) -&gt; Result 3 (zoom=1.6x, override=ZOOM)
+ * -&gt; Result 4 (zoom=1.8x, override=ZOOM)
+ * -&gt; Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>The application can turn on settings override and use zoom as normal. The example
+ * shows that the later zoom values (1.2x, 1.4x, 1.6x, and 1.8x) overwrite the zoom
+ * values (1.0x, 1.2x, 1.4x, and 1.8x) of earlier requests (#1, #2, #3, and #4).</p>
+ * <p>The application must make sure the settings override doesn't interfere with user
+ * journeys requiring simultaneous application of all controls in CaptureRequest on the
+ * requested output targets. For example, if the application takes a still capture using
+ * CameraCaptureSession#capture, and the repeating request immediately sets a different
+ * zoom value using override, the inflight still capture could have its zoom value
+ * overwritten unexpectedly.</p>
+ * <p>So the application is strongly recommended to turn off settingsOverride when taking
+ * still/burst captures, and turn it back on when there is only repeating viewfinder
+ * request and no inflight still/burst captures.</p>
+ * <p>Below is the example demonstrating the transitions in and out of the
+ * settings override:</p>
+ * <pre><code>Request 1 (zoom=1.0x, override=OFF)
+ * Request 2 (zoom=1.2x, override=OFF)
+ * Request 3 (zoom=1.4x, override=ZOOM) -&gt; Result 1 (zoom=1.0x, override=OFF)
+ * Request 4 (zoom=1.6x, override=ZOOM) -&gt; Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=OFF) -&gt; Result 3 (zoom=1.6x, override=ZOOM)
+ * -&gt; Result 4 (zoom=1.6x, override=OFF)
+ * -&gt; Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>This example shows that:</p>
+ * <ul>
+ * <li>The application "ramps in" settings override by setting the control to ZOOM.
+ * In the example, request #3 enables zoom settings override. Because the camera device
+ * can speed up applying zoom by 1 frame, the outputs of request #2 has 1.4x zoom, the
+ * value specified in request #3.</li>
+ * <li>The application "ramps out" of settings override by setting the control to OFF. In
+ * the example, request #5 changes the override to OFF. Because request #4's zoom
+ * takes effect in result #3, result #4's zoom remains the same until new value takes
+ * effect in result #5.</li>
+ * </ul>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_OFF OFF}</li>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_ZOOM ZOOM}</li>
+ * </ul>
+ *
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see #CONTROL_SETTINGS_OVERRIDE_OFF
+ * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_SETTINGS_OVERRIDE =
+ new Key<Integer>("android.control.settingsOverride", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1a15596af566..c4f0cabc5f78 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2633,6 +2633,90 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
new Key<Float>("android.control.zoomRatio", float.class);
/**
+ * <p>The desired CaptureRequest settings override with which certain keys are
+ * applied earlier so that they can take effect sooner.</p>
+ * <p>There are some CaptureRequest keys which can be applied earlier than others
+ * when controls within a CaptureRequest aren't required to take effect at the same time.
+ * One such example is zoom. Zoom can be applied at a later stage of the camera pipeline.
+ * As soon as the camera device receives the CaptureRequest, it can apply the requested
+ * zoom value onto an earlier request that's already in the pipeline, thus improves zoom
+ * latency.</p>
+ * <p>This key's value in the capture result reflects whether the controls for this capture
+ * are overridden "by" a newer request. This means that if a capture request turns on
+ * settings override, the capture result of an earlier request will contain the key value
+ * of ZOOM. On the other hand, if a capture request has settings override turned on,
+ * but all newer requests have it turned off, the key's value in the capture result will
+ * be OFF because this capture isn't overridden by a newer capture. In the two examples
+ * below, the capture results columns illustrate the settingsOverride values in different
+ * scenarios.</p>
+ * <p>Assuming the zoom settings override can speed up by 1 frame, below example illustrates
+ * the speed-up at the start of capture session:</p>
+ * <pre><code>Camera session created
+ * Request 1 (zoom=1.0x, override=ZOOM) -&gt;
+ * Request 2 (zoom=1.2x, override=ZOOM) -&gt;
+ * Request 3 (zoom=1.4x, override=ZOOM) -&gt; Result 1 (zoom=1.2x, override=ZOOM)
+ * Request 4 (zoom=1.6x, override=ZOOM) -&gt; Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=ZOOM) -&gt; Result 3 (zoom=1.6x, override=ZOOM)
+ * -&gt; Result 4 (zoom=1.8x, override=ZOOM)
+ * -&gt; Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>The application can turn on settings override and use zoom as normal. The example
+ * shows that the later zoom values (1.2x, 1.4x, 1.6x, and 1.8x) overwrite the zoom
+ * values (1.0x, 1.2x, 1.4x, and 1.8x) of earlier requests (#1, #2, #3, and #4).</p>
+ * <p>The application must make sure the settings override doesn't interfere with user
+ * journeys requiring simultaneous application of all controls in CaptureRequest on the
+ * requested output targets. For example, if the application takes a still capture using
+ * CameraCaptureSession#capture, and the repeating request immediately sets a different
+ * zoom value using override, the inflight still capture could have its zoom value
+ * overwritten unexpectedly.</p>
+ * <p>So the application is strongly recommended to turn off settingsOverride when taking
+ * still/burst captures, and turn it back on when there is only repeating viewfinder
+ * request and no inflight still/burst captures.</p>
+ * <p>Below is the example demonstrating the transitions in and out of the
+ * settings override:</p>
+ * <pre><code>Request 1 (zoom=1.0x, override=OFF)
+ * Request 2 (zoom=1.2x, override=OFF)
+ * Request 3 (zoom=1.4x, override=ZOOM) -&gt; Result 1 (zoom=1.0x, override=OFF)
+ * Request 4 (zoom=1.6x, override=ZOOM) -&gt; Result 2 (zoom=1.4x, override=ZOOM)
+ * Request 5 (zoom=1.8x, override=OFF) -&gt; Result 3 (zoom=1.6x, override=ZOOM)
+ * -&gt; Result 4 (zoom=1.6x, override=OFF)
+ * -&gt; Result 5 (zoom=1.8x, override=OFF)
+ * </code></pre>
+ * <p>This example shows that:</p>
+ * <ul>
+ * <li>The application "ramps in" settings override by setting the control to ZOOM.
+ * In the example, request #3 enables zoom settings override. Because the camera device
+ * can speed up applying zoom by 1 frame, the outputs of request #2 has 1.4x zoom, the
+ * value specified in request #3.</li>
+ * <li>The application "ramps out" of settings override by setting the control to OFF. In
+ * the example, request #5 changes the override to OFF. Because request #4's zoom
+ * takes effect in result #3, result #4's zoom remains the same until new value takes
+ * effect in result #5.</li>
+ * </ul>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_OFF OFF}</li>
+ * <li>{@link #CONTROL_SETTINGS_OVERRIDE_ZOOM ZOOM}</li>
+ * </ul>
+ *
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES android.control.availableSettingsOverrides}</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_SETTINGS_OVERRIDES
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see #CONTROL_SETTINGS_OVERRIDE_OFF
+ * @see #CONTROL_SETTINGS_OVERRIDE_ZOOM
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_SETTINGS_OVERRIDE =
+ new Key<Integer>("android.control.settingsOverride", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
index 97ce183ac191..84ca2b63fbcf 100644
--- a/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
+++ b/core/java/android/hardware/camera2/extension/CameraSessionConfig.aidl
@@ -24,4 +24,5 @@ parcelable CameraSessionConfig
List<CameraOutputConfig> outputConfigs;
CameraMetadataNative sessionParameter;
int sessionTemplateId;
+ int sessionType;
}
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index a8a7866e5ca4..3a0c3a5487e2 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -31,6 +31,7 @@ interface IImageCaptureExtenderImpl
@nullable CaptureStageImpl onPresetSession();
@nullable CaptureStageImpl onEnableSession();
@nullable CaptureStageImpl onDisableSession();
+ int getSessionType();
boolean isExtensionAvailable(in String cameraId, in CameraMetadataNative chars);
void init(in String cameraId, in CameraMetadataNative chars);
diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
index 2d673448fe27..01046d01233c 100644
--- a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl
@@ -34,6 +34,7 @@ interface IPreviewExtenderImpl
void init(in String cameraId, in CameraMetadataNative chars);
boolean isExtensionAvailable(in String cameraId, in CameraMetadataNative chars);
@nullable CaptureStageImpl getCaptureStage();
+ int getSessionType();
const int PROCESSOR_TYPE_REQUEST_UPDATE_ONLY = 0;
const int PROCESSOR_TYPE_IMAGE_PROCESSOR = 1;
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index c8dc2d0b0b91..01c1ef4136e6 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -243,9 +243,16 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
mCameraConfigMap.put(cameraOutput.getSurface(), output);
}
- SessionConfiguration sessionConfiguration = new SessionConfiguration(
- SessionConfiguration.SESSION_REGULAR, outputList,
- new CameraExtensionUtils.HandlerExecutor(mHandler), new SessionStateHandler());
+ int sessionType = SessionConfiguration.SESSION_REGULAR;
+ if (sessionConfig.sessionType != -1 &&
+ (sessionConfig.sessionType != SessionConfiguration.SESSION_HIGH_SPEED)) {
+ sessionType = sessionConfig.sessionType;
+ Log.v(TAG, "Using session type: " + sessionType);
+ }
+
+ SessionConfiguration sessionConfiguration = new SessionConfiguration(sessionType,
+ outputList, new CameraExtensionUtils.HandlerExecutor(mHandler),
+ new SessionStateHandler());
if ((sessionConfig.sessionParameter != null) &&
(!sessionConfig.sessionParameter.isEmpty())) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 41822e77f953..1f9f3b88e41d 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -415,6 +415,18 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
"Session already initialized");
return;
}
+ int previewSessionType = mPreviewExtender.getSessionType();
+ int imageSessionType = mImageExtender.getSessionType();
+ if (previewSessionType != imageSessionType) {
+ throw new IllegalStateException("Preview extender session type: " + previewSessionType +
+ "and image extender session type: " + imageSessionType + " mismatch!");
+ }
+ int sessionType = SessionConfiguration.SESSION_REGULAR;
+ if ((previewSessionType != -1) &&
+ (previewSessionType != SessionConfiguration.SESSION_HIGH_SPEED)) {
+ sessionType = previewSessionType;
+ Log.v(TAG, "Using session type: " + sessionType);
+ }
ArrayList<CaptureStageImpl> sessionParamsList = new ArrayList<>();
ArrayList<OutputConfiguration> outputList = new ArrayList<>();
@@ -432,7 +444,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
SessionConfiguration sessionConfig = new SessionConfiguration(
- SessionConfiguration.SESSION_REGULAR,
+ sessionType,
outputList,
new CameraExtensionUtils.HandlerExecutor(mHandler),
new SessionStateHandler());
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5403f089b308..3c73eb697a69 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -918,6 +918,22 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
+ /**
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ if (mService == null) {
+ Slog.w(TAG, "setUdfpsOverlay: no fingerprint service");
+ return;
+ }
+
+ try {
+ mService.setUdfpsOverlay(controller);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
/**
* Forwards BiometricStateListener to FingerprintService
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 051e3a4caa4e..365a6b3e06e5 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -26,6 +26,7 @@ import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import java.util.List;
@@ -201,6 +202,10 @@ interface IFingerprintService {
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void setSidefpsController(in ISidefpsController controller);
+ // Sets the controller for managing the UDFPS overlay.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void setUdfpsOverlay(in IUdfpsOverlay controller);
+
// Registers BiometricStateListener.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/services/core/java/com/android/server/pm/CommitRequest.java b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
index d1a6002590f2..c99fcccc68ea 100644
--- a/services/core/java/com/android/server/pm/CommitRequest.java
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
@@ -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,22 +14,16 @@
* limitations under the License.
*/
-package com.android.server.pm;
-
-import android.annotation.NonNull;
-
-import java.util.Map;
+package android.hardware.fingerprint;
/**
- * Package state to commit to memory and disk after reconciliation has completed.
+ * Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
+ * @hide
*/
-final class CommitRequest {
- final Map<String, ReconciledPackage> mReconciledPackages;
- @NonNull final int[] mAllUsers;
+oneway interface IUdfpsOverlay {
+ // Shows the overlay.
+ void show(long requestId, int sensorId, int reason);
- CommitRequest(Map<String, ReconciledPackage> reconciledPackages,
- @NonNull int[] allUsers) {
- mReconciledPackages = reconciledPackages;
- mAllUsers = allUsers;
- }
+ // Hides the overlay.
+ void hide(int sensorId);
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index f213224b981e..49c0f9261c53 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -161,4 +161,11 @@ interface IInputManager {
void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
+
+ // Get the bluetooth address of an input device if known, returning null if it either is not
+ // connected via bluetooth or if the address cannot be determined.
+ @EnforcePermission("BLUETOOTH")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.BLUETOOTH)")
+ String getInputDeviceBluetoothAddress(int deviceId);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8d4aac4bba88..0cf15f76103e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1481,6 +1481,24 @@ public final class InputManager {
}
/**
+ * Returns the Bluetooth address of this input device, if known.
+ *
+ * The returned string is always null if this input device is not connected
+ * via Bluetooth, or if the Bluetooth address of the device cannot be
+ * determined. The returned address will look like: "11:22:33:44:55:66".
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @Nullable
+ public String getInputDeviceBluetoothAddress(int deviceId) {
+ try {
+ return mIm.getInputDeviceBluetoothAddress(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets a vibrator service associated with an input device, always creates a new instance.
* @return The vibrator, never null.
* @hide
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index d3a6323230a5..3c4abab346a4 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -562,7 +562,7 @@ public class Binder implements IBinder {
*
* @hide
*/
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
public final native void markVintfStability();
/**
@@ -1219,25 +1219,40 @@ public class Binder implements IBinder {
@UnsupportedAppUsage
private boolean execTransact(int code, long dataObj, long replyObj,
int flags) {
+
+ Parcel data = Parcel.obtain(dataObj);
+ Parcel reply = Parcel.obtain(replyObj);
+
// At that point, the parcel request headers haven't been parsed so we do not know what
// {@link WorkSource} the caller has set. Use calling UID as the default.
- final int callingUid = Binder.getCallingUid();
- final long origWorkSource = ThreadLocalWorkSource.setUid(callingUid);
+ //
+ // TODO: this is wrong - we should attribute along the entire call route
+ // also this attribution logic should move to native code - it only works
+ // for Java now
+ //
+ // This attribution support is not generic and therefore not support in RPC mode
+ final int callingUid = data.isForRpc() ? -1 : Binder.getCallingUid();
+ final long origWorkSource = callingUid == -1
+ ? -1 : ThreadLocalWorkSource.setUid(callingUid);
+
try {
- return execTransactInternal(code, dataObj, replyObj, flags, callingUid);
+ return execTransactInternal(code, data, reply, flags, callingUid);
} finally {
- ThreadLocalWorkSource.restore(origWorkSource);
+ reply.recycle();
+ data.recycle();
+
+ if (callingUid != -1) {
+ ThreadLocalWorkSource.restore(origWorkSource);
+ }
}
}
- private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,
+ private boolean execTransactInternal(int code, Parcel data, Parcel reply, int flags,
int callingUid) {
// Make sure the observer won't change while processing a transaction.
final BinderInternal.Observer observer = sObserver;
final CallSession callSession =
observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;
- Parcel data = Parcel.obtain(dataObj);
- Parcel reply = Parcel.obtain(replyObj);
// Theoretically, we should call transact, which will call onTransact,
// but all that does is rewind it, and we just got these from an IPC,
// so we'll just call it directly.
@@ -1268,8 +1283,10 @@ public class Binder implements IBinder {
final boolean tracingEnabled = tagEnabled && transactionTraceName != null;
try {
+ // TODO - this logic should not be in Java - it should be in native
+ // code in libbinder so that it works for all binder users.
final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher;
- if (heavyHitterWatcher != null) {
+ if (heavyHitterWatcher != null && callingUid != -1) {
// Notify the heavy hitter watcher, if it's enabled.
heavyHitterWatcher.onTransaction(callingUid, getClass(), code);
}
@@ -1277,7 +1294,10 @@ public class Binder implements IBinder {
Trace.traceBegin(Trace.TRACE_TAG_AIDL, transactionTraceName);
}
- if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
+ // TODO - this logic should not be in Java - it should be in native
+ // code in libbinder so that it works for all binder users. Further,
+ // this should not re-use flags.
+ if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0 && callingUid != -1) {
AppOpsManager.startNotedAppOpsCollection(callingUid);
try {
res = onTransact(code, data, reply, flags);
@@ -1320,8 +1340,6 @@ public class Binder implements IBinder {
}
checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
- reply.recycle();
- data.recycle();
}
// Just in case -- we are done with the IPC, so there should be no more strict
diff --git a/core/java/android/os/BundleMerger.java b/core/java/android/os/BundleMerger.java
new file mode 100644
index 000000000000..51bd4ea75005
--- /dev/null
+++ b/core/java/android/os/BundleMerger.java
@@ -0,0 +1,379 @@
+/*
+ * 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Objects;
+import java.util.function.BinaryOperator;
+
+/**
+ * Configured rules for merging two {@link Bundle} instances.
+ * <p>
+ * By default, values from both {@link Bundle} instances are blended together on
+ * a key-wise basis, and conflicting value definitions for a key are dropped.
+ * <p>
+ * Nuanced strategies for handling conflicting value definitions can be applied
+ * using {@link #setMergeStrategy(String, int)} and
+ * {@link #setDefaultMergeStrategy(int)}.
+ * <p>
+ * When conflicting values have <em>inconsistent</em> data types (such as trying
+ * to merge a {@link String} and a {@link Integer}), both conflicting values are
+ * rejected and the key becomes undefined, regardless of the requested strategy.
+ *
+ * @hide
+ */
+public class BundleMerger implements Parcelable {
+ private static final String TAG = "BundleMerger";
+
+ private @Strategy int mDefaultStrategy = STRATEGY_REJECT;
+
+ private final ArrayMap<String, Integer> mStrategies = new ArrayMap<>();
+
+ /**
+ * Merge strategy that rejects both conflicting values.
+ */
+ public static final int STRATEGY_REJECT = 0;
+
+ /**
+ * Merge strategy that selects the first of conflicting values.
+ */
+ public static final int STRATEGY_FIRST = 1;
+
+ /**
+ * Merge strategy that selects the last of conflicting values.
+ */
+ public static final int STRATEGY_LAST = 2;
+
+ /**
+ * Merge strategy that selects the "minimum" of conflicting values which are
+ * {@link Comparable} with each other.
+ */
+ public static final int STRATEGY_COMPARABLE_MIN = 3;
+
+ /**
+ * Merge strategy that selects the "maximum" of conflicting values which are
+ * {@link Comparable} with each other.
+ */
+ public static final int STRATEGY_COMPARABLE_MAX = 4;
+
+ /**
+ * Merge strategy that numerically adds both conflicting values.
+ */
+ public static final int STRATEGY_NUMBER_ADD = 5;
+
+ /**
+ * Merge strategy that numerically increments the first conflicting value by
+ * {@code 1} and ignores the last conflicting value.
+ */
+ public static final int STRATEGY_NUMBER_INCREMENT_FIRST = 6;
+
+ /**
+ * Merge strategy that combines conflicting values using a boolean "and"
+ * operation.
+ */
+ public static final int STRATEGY_BOOLEAN_AND = 7;
+
+ /**
+ * Merge strategy that combines conflicting values using a boolean "or"
+ * operation.
+ */
+ public static final int STRATEGY_BOOLEAN_OR = 8;
+
+ /**
+ * Merge strategy that combines two conflicting array values by appending
+ * the last array after the first array.
+ */
+ public static final int STRATEGY_ARRAY_APPEND = 9;
+
+ /**
+ * Merge strategy that combines two conflicting {@link ArrayList} values by
+ * appending the last {@link ArrayList} after the first {@link ArrayList}.
+ */
+ public static final int STRATEGY_ARRAY_LIST_APPEND = 10;
+
+ @IntDef(flag = false, prefix = { "STRATEGY_" }, value = {
+ STRATEGY_REJECT,
+ STRATEGY_FIRST,
+ STRATEGY_LAST,
+ STRATEGY_COMPARABLE_MIN,
+ STRATEGY_COMPARABLE_MAX,
+ STRATEGY_NUMBER_ADD,
+ STRATEGY_NUMBER_INCREMENT_FIRST,
+ STRATEGY_BOOLEAN_AND,
+ STRATEGY_BOOLEAN_OR,
+ STRATEGY_ARRAY_APPEND,
+ STRATEGY_ARRAY_LIST_APPEND,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Strategy {}
+
+ /**
+ * Create a empty set of rules for merging two {@link Bundle} instances.
+ */
+ public BundleMerger() {
+ }
+
+ private BundleMerger(@NonNull Parcel in) {
+ mDefaultStrategy = in.readInt();
+ final int N = in.readInt();
+ for (int i = 0; i < N; i++) {
+ mStrategies.put(in.readString(), in.readInt());
+ }
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mDefaultStrategy);
+ final int N = mStrategies.size();
+ out.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ out.writeString(mStrategies.keyAt(i));
+ out.writeInt(mStrategies.valueAt(i));
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Configure the default merge strategy to be used when there isn't a
+ * more-specific strategy defined for a particular key via
+ * {@link #setMergeStrategy(String, int)}.
+ */
+ public void setDefaultMergeStrategy(@Strategy int strategy) {
+ mDefaultStrategy = strategy;
+ }
+
+ /**
+ * Configure the merge strategy to be used for the given key.
+ * <p>
+ * Subsequent calls for the same key will overwrite any previously
+ * configured strategy.
+ */
+ public void setMergeStrategy(@NonNull String key, @Strategy int strategy) {
+ mStrategies.put(key, strategy);
+ }
+
+ /**
+ * Return the merge strategy to be used for the given key, as defined by
+ * {@link #setMergeStrategy(String, int)}.
+ * <p>
+ * If no specific strategy has been configured for the given key, this
+ * returns {@link #setDefaultMergeStrategy(int)}.
+ */
+ public @Strategy int getMergeStrategy(@NonNull String key) {
+ return (int) mStrategies.getOrDefault(key, mDefaultStrategy);
+ }
+
+ /**
+ * Return a {@link BinaryOperator} which applies the strategies configured
+ * in this object to merge the two given {@link Bundle} arguments.
+ */
+ public BinaryOperator<Bundle> asBinaryOperator() {
+ return this::merge;
+ }
+
+ /**
+ * Apply the strategies configured in this object to merge the two given
+ * {@link Bundle} arguments.
+ *
+ * @return the merged {@link Bundle} result. If one argument is {@code null}
+ * it will return the other argument. If both arguments are null it
+ * will return {@code null}.
+ */
+ @SuppressWarnings("deprecation")
+ public @Nullable Bundle merge(@Nullable Bundle first, @Nullable Bundle last) {
+ if (first == null && last == null) {
+ return null;
+ }
+ if (first == null) {
+ first = Bundle.EMPTY;
+ }
+ if (last == null) {
+ last = Bundle.EMPTY;
+ }
+
+ // Start by bulk-copying all values without attempting to unpack any
+ // custom parcelables; we'll circle back to handle conflicts below
+ final Bundle res = new Bundle();
+ res.putAll(first);
+ res.putAll(last);
+
+ final ArraySet<String> conflictingKeys = new ArraySet<>();
+ conflictingKeys.addAll(first.keySet());
+ conflictingKeys.retainAll(last.keySet());
+ for (int i = 0; i < conflictingKeys.size(); i++) {
+ final String key = conflictingKeys.valueAt(i);
+ final int strategy = getMergeStrategy(key);
+ final Object firstValue = first.get(key);
+ final Object lastValue = last.get(key);
+ try {
+ res.putObject(key, merge(strategy, firstValue, lastValue));
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to merge key " + key + " with " + firstValue + " and "
+ + lastValue + " using strategy " + strategy, e);
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Merge the two given values. If only one of the values is defined, it
+ * always wins, otherwise the given strategy is applied.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static @Nullable Object merge(@Strategy int strategy,
+ @Nullable Object first, @Nullable Object last) {
+ if (first == null) return last;
+ if (last == null) return first;
+
+ if (first.getClass() != last.getClass()) {
+ throw new IllegalArgumentException("Merging requires consistent classes; first "
+ + first.getClass() + " last " + last.getClass());
+ }
+
+ switch (strategy) {
+ case STRATEGY_REJECT:
+ // Only actually reject when the values are different
+ if (Objects.deepEquals(first, last)) {
+ return first;
+ } else {
+ return null;
+ }
+ case STRATEGY_FIRST:
+ return first;
+ case STRATEGY_LAST:
+ return last;
+ case STRATEGY_COMPARABLE_MIN:
+ return comparableMin(first, last);
+ case STRATEGY_COMPARABLE_MAX:
+ return comparableMax(first, last);
+ case STRATEGY_NUMBER_ADD:
+ return numberAdd(first, last);
+ case STRATEGY_NUMBER_INCREMENT_FIRST:
+ return numberIncrementFirst(first, last);
+ case STRATEGY_BOOLEAN_AND:
+ return booleanAnd(first, last);
+ case STRATEGY_BOOLEAN_OR:
+ return booleanOr(first, last);
+ case STRATEGY_ARRAY_APPEND:
+ return arrayAppend(first, last);
+ case STRATEGY_ARRAY_LIST_APPEND:
+ return arrayListAppend(first, last);
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object comparableMin(@NonNull Object first, @NonNull Object last) {
+ return ((Comparable<Object>) first).compareTo(last) < 0 ? first : last;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object comparableMax(@NonNull Object first, @NonNull Object last) {
+ return ((Comparable<Object>) first).compareTo(last) >= 0 ? first : last;
+ }
+
+ private static @NonNull Object numberAdd(@NonNull Object first, @NonNull Object last) {
+ if (first instanceof Integer) {
+ return ((Integer) first) + ((Integer) last);
+ } else if (first instanceof Long) {
+ return ((Long) first) + ((Long) last);
+ } else if (first instanceof Float) {
+ return ((Float) first) + ((Float) last);
+ } else if (first instanceof Double) {
+ return ((Double) first) + ((Double) last);
+ } else {
+ throw new IllegalArgumentException("Unable to add " + first.getClass());
+ }
+ }
+
+ private static @NonNull Number numberIncrementFirst(@NonNull Object first,
+ @NonNull Object last) {
+ if (first instanceof Integer) {
+ return ((Integer) first) + 1;
+ } else if (first instanceof Long) {
+ return ((Long) first) + 1L;
+ } else {
+ throw new IllegalArgumentException("Unable to add " + first.getClass());
+ }
+ }
+
+ private static @NonNull Object booleanAnd(@NonNull Object first, @NonNull Object last) {
+ return ((Boolean) first) && ((Boolean) last);
+ }
+
+ private static @NonNull Object booleanOr(@NonNull Object first, @NonNull Object last) {
+ return ((Boolean) first) || ((Boolean) last);
+ }
+
+ private static @NonNull Object arrayAppend(@NonNull Object first, @NonNull Object last) {
+ if (!first.getClass().isArray()) {
+ throw new IllegalArgumentException("Unable to append " + first.getClass());
+ }
+ final Class<?> clazz = first.getClass().getComponentType();
+ final int firstLength = Array.getLength(first);
+ final int lastLength = Array.getLength(last);
+ final Object res = Array.newInstance(clazz, firstLength + lastLength);
+ System.arraycopy(first, 0, res, 0, firstLength);
+ System.arraycopy(last, 0, res, firstLength, lastLength);
+ return res;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static @NonNull Object arrayListAppend(@NonNull Object first, @NonNull Object last) {
+ if (!(first instanceof ArrayList)) {
+ throw new IllegalArgumentException("Unable to append " + first.getClass());
+ }
+ final ArrayList<Object> firstList = (ArrayList<Object>) first;
+ final ArrayList<Object> lastList = (ArrayList<Object>) last;
+ final ArrayList<Object> res = new ArrayList<>(firstList.size() + lastList.size());
+ res.addAll(firstList);
+ res.addAll(lastList);
+ return res;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<BundleMerger> CREATOR =
+ new Parcelable.Creator<BundleMerger>() {
+ @Override
+ public BundleMerger createFromParcel(Parcel in) {
+ return new BundleMerger(in);
+ }
+
+ @Override
+ public BundleMerger[] newArray(int size) {
+ return new BundleMerger[size];
+ }
+ };
+}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d451765f022f..2afa87980c11 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -367,6 +367,8 @@ public final class Parcel {
@FastNative
private static native void nativeMarkForBinder(long nativePtr, IBinder binder);
@CriticalNative
+ private static native boolean nativeIsForRpc(long nativePtr);
+ @CriticalNative
private static native int nativeDataSize(long nativePtr);
@CriticalNative
private static native int nativeDataAvail(long nativePtr);
@@ -644,6 +646,15 @@ public final class Parcel {
nativeMarkForBinder(mNativePtr, binder);
}
+ /**
+ * Whether this Parcel is written for an RPC transaction.
+ *
+ * @hide
+ */
+ public final boolean isForRpc() {
+ return nativeIsForRpc(mNativePtr);
+ }
+
/** @hide */
@ParcelFlags
@TestApi
diff --git a/core/java/android/os/PermissionEnforcer.java b/core/java/android/os/PermissionEnforcer.java
new file mode 100644
index 000000000000..221e89a6a76f
--- /dev/null
+++ b/core/java/android/os/PermissionEnforcer.java
@@ -0,0 +1,101 @@
+/*
+ * 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.os;
+
+import android.annotation.NonNull;
+import android.annotation.SystemService;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.permission.PermissionCheckerManager;
+
+/**
+ * PermissionEnforcer check permissions for AIDL-generated services which use
+ * the @EnforcePermission annotation.
+ *
+ * <p>AIDL services may be annotated with @EnforcePermission which will trigger
+ * the generation of permission check code. This generated code relies on
+ * PermissionEnforcer to validate the permissions. The methods available are
+ * purposely similar to the AIDL annotation syntax.
+ *
+ * @see android.permission.PermissionManager
+ *
+ * @hide
+ */
+@SystemService(Context.PERMISSION_ENFORCER_SERVICE)
+public class PermissionEnforcer {
+
+ private final Context mContext;
+
+ /** Protected constructor. Allows subclasses to instantiate an object
+ * without using a Context.
+ */
+ protected PermissionEnforcer() {
+ mContext = null;
+ }
+
+ /** Constructor, prefer using the fromContext static method when possible */
+ public PermissionEnforcer(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @PermissionCheckerManager.PermissionResult
+ protected int checkPermission(@NonNull String permission, @NonNull AttributionSource source) {
+ return PermissionChecker.checkPermissionForDataDelivery(
+ mContext, permission, PermissionChecker.PID_UNKNOWN, source, "" /* message */);
+ }
+
+ public void enforcePermission(@NonNull String permission, @NonNull
+ AttributionSource source) throws SecurityException {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Access denied, requires: " + permission);
+ }
+ }
+
+ public void enforcePermissionAllOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result != PermissionCheckerManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Access denied, requires: allOf={"
+ + String.join(", ", permissions) + "}");
+ }
+ }
+ }
+
+ public void enforcePermissionAnyOf(@NonNull String[] permissions,
+ @NonNull AttributionSource source) throws SecurityException {
+ for (String permission : permissions) {
+ int result = checkPermission(permission, source);
+ if (result == PermissionCheckerManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+ throw new SecurityException("Access denied, requires: anyOf={"
+ + String.join(", ", permissions) + "}");
+ }
+
+ /**
+ * Returns a new PermissionEnforcer based on a Context.
+ *
+ * @hide
+ */
+ public static PermissionEnforcer fromContext(@NonNull Context context) {
+ return context.getSystemService(PermissionEnforcer.class);
+ }
+}
diff --git a/core/java/android/os/ServiceManagerNative.java b/core/java/android/os/ServiceManagerNative.java
index 2dcf67483203..f2143f63d1ee 100644
--- a/core/java/android/os/ServiceManagerNative.java
+++ b/core/java/android/os/ServiceManagerNative.java
@@ -98,6 +98,10 @@ class ServiceManagerProxy implements IServiceManager {
return mServiceManager.updatableViaApex(name);
}
+ public String[] getUpdatableNames(String apexName) throws RemoteException {
+ return mServiceManager.getUpdatableNames(apexName);
+ }
+
public ConnectionInfo getConnectionInfo(String name) throws RemoteException {
return mServiceManager.getConnectionInfo(name);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cd2bbebf3d4d..897b7c3afe54 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3374,9 +3374,26 @@ public final class Settings {
}
}
- // Fetch all flags for the namespace at once for caching purposes
- Bundle b = cp.call(cr.getAttributionSource(),
- mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+ Bundle b;
+ // b/252663068: if we're in system server and the caller did not call
+ // clearCallingIdentity, the read would fail due to mismatched AttributionSources.
+ // TODO(b/256013480): remove this bypass after fixing the callers in system server.
+ if (namespace.equals(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER)
+ && Settings.isInSystemServer()
+ && Binder.getCallingUid() != Process.myUid()) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Fetch all flags for the namespace at once for caching purposes
+ b = cp.call(cr.getAttributionSource(),
+ mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ // Fetch all flags for the namespace at once for caching purposes
+ b = cp.call(cr.getAttributionSource(),
+ mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+ }
if (b == null) {
// Invalid response, return an empty map
return keyValues;
@@ -7135,7 +7152,7 @@ public final class Settings {
* Format like "ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0"
* where imeId is ComponentName and subtype is int32.
*/
- @Readable
+ @Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String ENABLED_INPUT_METHODS = "enabled_input_methods";
/**
@@ -7144,7 +7161,7 @@ public final class Settings {
* by ':'.
* @hide
*/
- @Readable
+ @Readable(maxTargetSdk = Build.VERSION_CODES.TIRAMISU)
public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods";
/**
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 295171ca9bbd..5f30ad054c50 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -54,6 +54,13 @@ public abstract class DreamManagerInternal {
public abstract void requestDream();
/**
+ * Whether dreaming can start given user settings and the current dock/charge state.
+ *
+ * @param isScreenOn True if the screen is currently on.
+ */
+ public abstract boolean canStartDreaming(boolean isScreenOn);
+
+ /**
* Called by the ActivityTaskManagerService to verify that the startDreamActivity
* request comes from the current active dream component.
*
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index a59d429b24e6..c6cd708c5967 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -81,7 +81,6 @@ import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.MotionEvent;
import android.view.PixelCopy;
import android.view.Surface;
@@ -251,7 +250,6 @@ public abstract class WallpaperService extends Service {
final Rect mDispatchedStableInsets = new Rect();
DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT;
final InsetsState mInsetsState = new InsetsState();
- final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
final Bundle mSyncSeqIdBundle = new Bundle();
@@ -1133,8 +1131,9 @@ public abstract class WallpaperService extends Service {
InputChannel inputChannel = new InputChannel();
if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
- mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel,
- mInsetsState, mTempControls, new Rect(), new float[1]) < 0) {
+ mDisplay.getDisplayId(), WindowInsets.Type.defaultVisible(),
+ inputChannel, mInsetsState, mTempControls, new Rect(),
+ new float[1]) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index d4bcd12abd96..01989d54b871 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -229,6 +229,8 @@ public abstract class BaseKeyListener extends MetaKeyKeyListener
break;
} else if (Emoji.isEmojiModifierBase(codePoint)) {
deleteCharCount += Character.charCount(codePoint);
+ state = STATE_BEFORE_EMOJI;
+ break;
}
state = STATE_FINISHED;
break;
diff --git a/core/java/android/util/CharsetUtils.java b/core/java/android/util/CharsetUtils.java
index 3b08c3b6d52f..7c83087d32e5 100644
--- a/core/java/android/util/CharsetUtils.java
+++ b/core/java/android/util/CharsetUtils.java
@@ -18,6 +18,8 @@ package android.util;
import android.annotation.NonNull;
+import com.android.modules.utils.ModifiedUtf8;
+
import dalvik.annotation.optimization.FastNative;
/**
@@ -30,8 +32,7 @@ import dalvik.annotation.optimization.FastNative;
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by {@code kUtfUse4ByteSequence}
* in {@code art/runtime/jni/jni_internal.cc}. If precise modified UTF-8
- * encoding is required, use {@link com.android.internal.util.ModifiedUtf8}
- * instead.
+ * encoding is required, use {@link ModifiedUtf8} instead.
*
* @hide
*/
@@ -43,8 +44,8 @@ public class CharsetUtils {
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
- * If precise modified UTF-8 encoding is required, use
- * {@link com.android.internal.util.ModifiedUtf8} instead.
+ * If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
+ * instead.
*
* @param src string value to be encoded
* @param dest destination byte array to encode into
@@ -66,8 +67,8 @@ public class CharsetUtils {
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
- * If precise modified UTF-8 encoding is required, use
- * {@link com.android.internal.util.ModifiedUtf8} instead.
+ * If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
+ * instead.
*
* @param src string value to be encoded
* @param srcLen exact length of string to be encoded
@@ -88,8 +89,8 @@ public class CharsetUtils {
* Callers are cautioned that there is a long-standing ART bug that emits
* non-standard 4-byte sequences, as described by
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
- * If precise modified UTF-8 encoding is required, use
- * {@link com.android.internal.util.ModifiedUtf8} instead.
+ * If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
+ * instead.
*
* @param src source byte array to decode from
* @param srcOff offset into source where decoding should begin
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 0a3e6b1cff38..517d98222093 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -174,6 +174,14 @@ public class DisplayMetrics {
* This is not a density that applications should target, instead relying
* on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
*/
+ public static final int DENSITY_520 = 520;
+
+ /**
+ * Intermediate density for screens that sit somewhere between
+ * {@link #DENSITY_XXHIGH} (480 dpi) and {@link #DENSITY_XXXHIGH} (640 dpi).
+ * This is not a density that applications should target, instead relying
+ * on the system to scale their {@link #DENSITY_XXXHIGH} assets for them.
+ */
public static final int DENSITY_560 = 560;
/**
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index d729352ab7c5..33058d84b2e5 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -24,9 +24,9 @@ import android.system.Os;
import com.android.internal.util.ArtBinaryXmlPullParser;
import com.android.internal.util.ArtBinaryXmlSerializer;
-import com.android.internal.util.BinaryXmlSerializer;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BinaryXmlSerializer;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index 1940042a1052..0769f1209a2b 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -19,7 +19,6 @@ package android.view;
import android.content.ComponentName;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
/**
* Singular controller of insets to use when there isn't another obvious controller available.
@@ -32,10 +31,9 @@ oneway interface IDisplayWindowInsetsController {
* Called when top focused window changes to determine whether or not to take over insets
* control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
* @param component: Passes the top application component in the focused window.
- * @param requestedVisibilities The insets visibilities requested by the focussed window.
+ * @param requestedVisibleTypes The insets types requested visible by the focused window.
*/
- void topFocusedWindowChanged(in ComponentName component,
- in InsetsVisibilities insetsVisibilities);
+ void topFocusedWindowChanged(in ComponentName component, int requestedVisibleTypes);
/**
* @see IWindow#insetsChanged
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index dddbe395aef1..e2bc5668d058 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -721,7 +721,7 @@ interface IWindowManager
* Called when a remote process updates the requested visibilities of insets on a display window
* container.
*/
- void updateDisplayWindowRequestedVisibilities(int displayId, in InsetsVisibilities vis);
+ void updateDisplayWindowRequestedVisibleTypes(int displayId, int requestedVisibleTypes);
/**
* Called to get the expected window insets.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 0052e82c2448..03ccb47305e0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -32,7 +32,6 @@ import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -48,15 +47,15 @@ import java.util.List;
*/
interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
+ in int viewVisibility, in int layerStackId, int requestedVisibleTypes,
out InputChannel outInputChannel, out InsetsState insetsState,
out InsetsSourceControl[] activeControls, out Rect attachedFrame,
out float[] sizeCompatScale);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, in int userId,
- in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
- out InsetsState insetsState, out InsetsSourceControl[] activeControls,
- out Rect attachedFrame, out float[] sizeCompatScale);
+ in int viewVisibility, in int layerStackId, in int userId, int requestedVisibleTypes,
+ out InputChannel outInputChannel, out InsetsState insetsState,
+ out InsetsSourceControl[] activeControls, out Rect attachedFrame,
+ out float[] sizeCompatScale);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, out InsetsState insetsState,
out Rect attachedFrame, out float[] sizeCompatScale);
@@ -279,9 +278,9 @@ interface IWindowSession {
oneway void updateTapExcludeRegion(IWindow window, in Region region);
/**
- * Updates the requested visibilities of insets.
+ * Updates the requested visible types of insets.
*/
- oneway void updateRequestedVisibilities(IWindow window, in InsetsVisibilities visibilities);
+ oneway void updateRequestedVisibleTypes(IWindow window, int requestedVisibleTypes);
/**
* Called when the system gesture exclusion has changed.
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 9b1d8673390b..799955b1107a 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,6 +16,7 @@
package android.view;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1010,6 +1011,22 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Returns the Bluetooth address of this input device, if known.
+ *
+ * The returned string is always null if this input device is not connected
+ * via Bluetooth, or if the Bluetooth address of the device cannot be
+ * determined. The returned address will look like: "11:22:33:44:55:66".
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @Nullable
+ public String getBluetoothAddress() {
+ // We query the address via a separate InputManager API instead of pre-populating it in
+ // this class to avoid leaking it to apps that do not have sufficient permissions.
+ return InputManager.getInstance().getInputDeviceBluetoothAddress(mId);
+ }
+
+ /**
* Gets the vibrator service associated with the device, if there is one.
* Even if the device does not have a vibrator, the result is never null.
* Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4a72a62b7a8d..8b38e9e25001 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -46,6 +46,7 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSourceConsumer.ShowResult;
import android.view.InsetsState.InternalInsetsType;
@@ -102,18 +103,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
void applySurfaceParams(final SyncRtSurfaceTransactionApplier.SurfaceParams... params);
/**
- * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+ * @see ViewRootImpl#updateCompatSysUiVisibility(int, int, int)
*/
- void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
- boolean hasControl);
+ default void updateCompatSysUiVisibility(@InsetsType int visibleTypes,
+ @InsetsType int requestedVisibleTypes, @InsetsType int controllableTypes) { }
/**
* Called when the requested visibilities of insets have been modified by the client.
* The visibilities should be reported back to WM.
*
- * @param visibilities A collection of the requested visibilities.
+ * @param types Bitwise flags of types requested visible.
*/
- void updateRequestedVisibilities(InsetsVisibilities visibilities);
+ void updateRequestedVisibleTypes(@InsetsType int types);
/**
* @return Whether the host has any callbacks it wants to synchronize the animations with.
@@ -564,9 +565,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/** The state dispatched from server */
private final InsetsState mLastDispatchedState = new InsetsState();
- /** The requested visibilities sent to server */
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
-
private final Rect mFrame = new Rect();
private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator;
private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
@@ -575,7 +573,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
- private final ArraySet<InsetsSourceConsumer> mRequestedVisibilityChanged = new ArraySet<>();
private WindowInsets mLastInsets;
private boolean mAnimCallbackScheduled;
@@ -593,6 +590,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private boolean mStartingAnimation;
private int mCaptionInsetsHeight = 0;
private boolean mAnimationsDisabled;
+ private boolean mCompatSysUiVisibilityStaled;
private final Runnable mPendingControlTimeout = this::abortPendingImeControlRequest;
private final ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
@@ -604,6 +602,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
/** Set of inset types which cannot be controlled by the user animation */
private @InsetsType int mDisabledUserAnimationInsetsTypes;
+ /** Set of inset types which are visible */
+ private @InsetsType int mVisibleTypes = WindowInsets.Type.defaultVisible();
+
+ /** Set of inset types which are requested visible */
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
+
+ /** Set of inset types which are requested visible which are reported to the host */
+ private @InsetsType int mReportedRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
+
+ /** Set of inset types that we have controls of */
+ private @InsetsType int mControllableTypes;
+
private final Runnable mInvokeControllableInsetsChangedListeners =
this::invokeControllableInsetsChangedListeners;
@@ -687,8 +697,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
@Override
- public boolean isRequestedVisible(int type) {
- return getSourceConsumer(type).isRequestedVisible();
+ public @InsetsType int getRequestedVisibleTypes() {
+ return mRequestedVisibleTypes;
}
public InsetsState getLastDispatchedState() {
@@ -715,6 +725,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final InsetsState lastState = new InsetsState(mState, true /* copySources */);
updateState(state);
applyLocalVisibilityOverride();
+ updateCompatSysUiVisibility();
if (!mState.equals(lastState, false /* excludingCaptionInsets */,
true /* excludeInvisibleIme */)) {
@@ -727,14 +738,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
private void updateState(InsetsState newState) {
mState.set(newState, 0 /* types */);
+ @InsetsType int visibleTypes = 0;
@InsetsType int disabledUserAnimationTypes = 0;
@InsetsType int[] cancelledUserAnimationTypes = {0};
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
InsetsSource source = newState.peekSource(type);
if (source == null) continue;
@AnimationType int animationType = getAnimationType(type);
+ @InsetsType int insetsType = toPublicType(type);
if (!source.isUserControllable()) {
- @InsetsType int insetsType = toPublicType(type);
// The user animation is not allowed when visible frame is empty.
disabledUserAnimationTypes |= insetsType;
if (animationType == ANIMATION_TYPE_USER) {
@@ -744,6 +756,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
}
getSourceConsumer(type).updateSource(source, animationType);
+ if (source.isVisible()) {
+ visibleTypes |= insetsType;
+ }
+ }
+ if (mVisibleTypes != visibleTypes) {
+ if (WindowInsets.Type.hasCompatSystemBars(mVisibleTypes ^ visibleTypes)) {
+ mCompatSysUiVisibilityStaled = true;
+ }
+ mVisibleTypes = visibleTypes;
}
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
// Only update the server side insets here.
@@ -829,7 +850,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
/**
- * @see InsetsState#calculateInsets
+ * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
+ * int, SparseIntArray)
*/
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars,
@@ -868,7 +890,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
}
- boolean requestedVisibilityStale = false;
+ @InsetsType int controllableTypes = 0;
final int[] showTypes = new int[1];
final int[] hideTypes = new int[1];
@@ -888,22 +910,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final @InternalInsetsType int type = control.getType();
final InsetsSourceConsumer consumer = getSourceConsumer(type);
consumer.setControl(control, showTypes, hideTypes);
-
- if (!requestedVisibilityStale) {
- final boolean requestedVisible = consumer.isRequestedVisible();
-
- // We might have changed our requested visibilities while we don't have the control,
- // so we need to update our requested state once we have control. Otherwise, our
- // requested state at the server side might be incorrect.
- final boolean requestedVisibilityChanged =
- requestedVisible != mRequestedVisibilities.getVisibility(type);
-
- // The IME client visibility will be reset by insets source provider while updating
- // control, so if IME is requested visible, we need to send the request to server.
- final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible;
-
- requestedVisibilityStale = requestedVisibilityChanged || imeRequestedVisible;
- }
+ controllableTypes |= InsetsState.toPublicType(type);
}
if (mTmpControlArray.size() > 0) {
@@ -927,8 +934,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
applyAnimation(hideTypes[0], false /* show */, false /* fromIme */);
}
+ if (mControllableTypes != controllableTypes) {
+ if (WindowInsets.Type.hasCompatSystemBars(mControllableTypes ^ controllableTypes)) {
+ mCompatSysUiVisibilityStaled = true;
+ }
+ mControllableTypes = controllableTypes;
+ }
+
// InsetsSourceConsumer#setControl might change the requested visibility.
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
}
@Override
@@ -1082,7 +1096,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
return;
}
@@ -1118,7 +1132,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
});
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
return;
}
@@ -1126,7 +1140,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (typesReady == 0) {
if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
listener.onCancelled(null);
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
return;
}
@@ -1158,7 +1172,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
} else {
hideDirectly(types, false /* animationFinished */, animationType, fromIme);
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
}
// TODO(b/242962223): Make this setter restrictive.
@@ -1386,11 +1400,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
/**
- * @see ViewRootImpl#updateCompatSysUiVisibility(int, boolean, boolean)
+ * @see ViewRootImpl#updateCompatSysUiVisibility(int, int, int)
*/
- public void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
- boolean hasControl) {
- mHost.updateCompatSysUiVisibility(type, visible, hasControl);
+ public void updateCompatSysUiVisibility() {
+ if (mCompatSysUiVisibilityStaled) {
+ mCompatSysUiVisibilityStaled = false;
+ mHost.updateCompatSysUiVisibility(
+ mVisibleTypes, mRequestedVisibleTypes, mControllableTypes);
+ }
}
/**
@@ -1420,35 +1437,27 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@VisibleForTesting
public void onRequestedVisibilityChanged(InsetsSourceConsumer consumer) {
- mRequestedVisibilityChanged.add(consumer);
+ final @InsetsType int type = InsetsState.toPublicType(consumer.getType());
+ final int requestedVisibleTypes = consumer.isRequestedVisible()
+ ? mRequestedVisibleTypes | type
+ : mRequestedVisibleTypes & ~type;
+ if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ if (WindowInsets.Type.hasCompatSystemBars(type)) {
+ mCompatSysUiVisibilityStaled = true;
+ }
+ }
}
/**
- * Sends the requested visibilities to window manager if any of them is changed.
+ * Sends the requested visible types to window manager if any of them is changed.
*/
- private void updateRequestedVisibilities() {
- boolean changed = false;
- for (int i = mRequestedVisibilityChanged.size() - 1; i >= 0; i--) {
- final InsetsSourceConsumer consumer = mRequestedVisibilityChanged.valueAt(i);
- final @InternalInsetsType int type = consumer.getType();
- if (type == ITYPE_CAPTION_BAR) {
- continue;
- }
- final boolean requestedVisible = consumer.isRequestedVisible();
- if (mRequestedVisibilities.getVisibility(type) != requestedVisible) {
- mRequestedVisibilities.setVisibility(type, requestedVisible);
- changed = true;
- }
- }
- mRequestedVisibilityChanged.clear();
- if (!changed) {
- return;
+ private void reportRequestedVisibleTypes() {
+ updateCompatSysUiVisibility();
+ if (mReportedRequestedVisibleTypes != mRequestedVisibleTypes) {
+ mReportedRequestedVisibleTypes = mRequestedVisibleTypes;
+ mHost.updateRequestedVisibleTypes(mReportedRequestedVisibleTypes);
}
- mHost.updateRequestedVisibilities(mRequestedVisibilities);
- }
-
- InsetsVisibilities getRequestedVisibilities() {
- return mRequestedVisibilities;
}
@VisibleForTesting
@@ -1504,7 +1513,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
if (fromIme) {
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
@@ -1520,7 +1529,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
for (int i = internalTypes.size() - 1; i >= 0; i--) {
getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
}
- updateRequestedVisibilities();
+ reportRequestedVisibleTypes();
if (fromIme) {
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 72757807169e..da54da16585d 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -273,6 +273,9 @@ public class InsetsFrameProvider implements Parcelable {
/**
* Class to describe the insets size to be provided to window with specific window type. If not
* used, same insets size will be sent as instructed in the insetsSize and source.
+ *
+ * If the insetsSize of given type is set to {@code null}, the insets source frame will be used
+ * directly for that window type.
*/
public static class InsetsSizeOverride implements Parcelable {
public final int windowType;
@@ -280,7 +283,7 @@ public class InsetsFrameProvider implements Parcelable {
protected InsetsSizeOverride(Parcel in) {
windowType = in.readInt();
- insetsSize = in.readParcelable(null, android.graphics.Insets.class);
+ insetsSize = in.readParcelable(null, Insets.class);
}
public InsetsSizeOverride(int type, Insets size) {
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 5236fe772a7b..7a498ad2358d 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -34,7 +34,6 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.graphics.Rect;
-import android.util.ArraySet;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsState.InternalInsetsType;
@@ -149,9 +148,6 @@ public class InsetsSourceConsumer {
source.setVisible(serverVisibility);
mController.notifyVisibilityChanged();
}
-
- // For updateCompatSysUiVisibility
- applyLocalVisibilityOverride();
} else {
final boolean requestedVisible = isRequestedVisibleAwaitingControl();
final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
@@ -259,8 +255,6 @@ public class InsetsSourceConsumer {
mController.getHost().getInputMethodManager(), null /* icProto */);
}
- updateCompatSysUiVisibility(hasControl, source, isVisible);
-
// If we don't have control, we are not able to change the visibility.
if (!hasControl) {
if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
@@ -277,36 +271,6 @@ public class InsetsSourceConsumer {
return true;
}
- private void updateCompatSysUiVisibility(boolean hasControl, InsetsSource source,
- boolean visible) {
- final @InsetsType int publicType = InsetsState.toPublicType(mType);
- if (publicType != WindowInsets.Type.statusBars()
- && publicType != WindowInsets.Type.navigationBars()) {
- // System UI visibility only controls status bars and navigation bars.
- return;
- }
- final boolean compatVisible;
- if (hasControl) {
- compatVisible = mRequestedVisible;
- } else if (source != null && !source.getFrame().isEmpty()) {
- compatVisible = visible;
- } else {
- final ArraySet<Integer> types = InsetsState.toInternalType(publicType);
- for (int i = types.size() - 1; i >= 0; i--) {
- final InsetsSource s = mState.peekSource(types.valueAt(i));
- if (s != null && !s.getFrame().isEmpty()) {
- // The compat system UI visibility would be updated by another consumer which
- // handles the same public insets type.
- return;
- }
- }
- // No one provides the public type. Use the requested visibility for making the callback
- // behavior compatible.
- compatVisible = mRequestedVisible;
- }
- mController.updateCompatSysUiVisibility(mType, compatVisible, hasControl);
- }
-
@VisibleForTesting
public boolean isRequestedVisible() {
return mRequestedVisible;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 9f426a176a08..e91839baad61 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -352,7 +352,7 @@ public class InsetsState implements Parcelable {
}
public Insets calculateInsets(Rect frame, @InsetsType int types,
- InsetsVisibilities overrideVisibilities) {
+ @InsetsType int requestedVisibleTypes) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
@@ -360,10 +360,7 @@ public class InsetsState implements Parcelable {
continue;
}
int publicType = InsetsState.toPublicType(type);
- if ((publicType & types) == 0) {
- continue;
- }
- if (!overrideVisibilities.getVisibility(type)) {
+ if ((publicType & types & requestedVisibleTypes) == 0) {
continue;
}
insets = Insets.max(source.calculateInsets(frame, true), insets);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ceab310b64b2..a08a5a8892c0 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1294,6 +1294,8 @@ public final class MotionEvent extends InputEvent implements Parcelable {
// NOTE: If you add a new axis here you must also add it to:
// frameworks/native/include/android/input.h
// frameworks/native/libs/input/InputEventLabels.cpp
+ // platform/cts/tests/tests/view/src/android/view/cts/MotionEventTest.java
+ // (testAxisFromToString)
// Symbolic names of all axes.
private static final SparseArray<String> AXIS_SYMBOLIC_NAMES = new SparseArray<String>();
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index 3fe9110283a6..e8f62fc0963f 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -45,6 +45,7 @@ public class PendingInsetsController implements WindowInsetsController {
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
private WindowInsetsAnimationControlListener mLoggingListener;
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
@Override
public void show(int types) {
@@ -52,6 +53,7 @@ public class PendingInsetsController implements WindowInsetsController {
mReplayedInsetsController.show(types);
} else {
mRequests.add(new ShowRequest(types));
+ mRequestedVisibleTypes |= types;
}
}
@@ -61,6 +63,7 @@ public class PendingInsetsController implements WindowInsetsController {
mReplayedInsetsController.hide(types);
} else {
mRequests.add(new HideRequest(types));
+ mRequestedVisibleTypes &= ~types;
}
}
@@ -122,11 +125,11 @@ public class PendingInsetsController implements WindowInsetsController {
}
@Override
- public boolean isRequestedVisible(int type) {
-
- // Method is only used once real insets controller is attached, so no need to traverse
- // requests here.
- return InsetsState.getDefaultVisibility(type);
+ public @InsetsType int getRequestedVisibleTypes() {
+ if (mReplayedInsetsController != null) {
+ return mReplayedInsetsController.getRequestedVisibleTypes();
+ }
+ return mRequestedVisibleTypes;
}
@Override
@@ -189,6 +192,7 @@ public class PendingInsetsController implements WindowInsetsController {
mAppearanceMask = 0;
mAnimationsDisabled = false;
mLoggingListener = null;
+ mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
// After replaying, we forward everything directly to the replayed instance.
mReplayedInsetsController = controller;
}
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index a52fc7586f72..6d25523ac5bc 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -183,6 +183,7 @@ public final class VelocityTracker {
private static native float nativeGetVelocity(long ptr, int axis, int id);
private static native boolean nativeGetEstimator(
long ptr, int axis, int id, Estimator outEstimator);
+ private static native boolean nativeIsAxisSupported(int axis);
static {
// Strategy string and IDs mapping lookup.
@@ -305,6 +306,22 @@ public final class VelocityTracker {
}
/**
+ * Checks whether a given motion axis is supported for velocity tracking.
+ *
+ * <p>The axis values that would make sense to use for this method are the ones defined in the
+ * {@link MotionEvent} class.
+ *
+ * @param axis The axis to check for velocity support.
+ * @return {@code true} if {@code axis} is supported for velocity tracking, or {@code false}
+ * otherwise.
+ * @see #getAxisVelocity(int, int)
+ * @see #getAxisVelocity(int)
+ */
+ public boolean isAxisSupported(int axis) {
+ return nativeIsAxisSupported(axis);
+ }
+
+ /**
* Reset the velocity tracker back to its initial state.
*/
public void clear() {
@@ -345,7 +362,9 @@ public final class VelocityTracker {
* {@link #getYVelocity()}.
*
* @param units The units you would like the velocity in. A value of 1
- * provides pixels per millisecond, 1000 provides pixels per second, etc.
+ * provides units per millisecond, 1000 provides units per second, etc.
+ * Note that the units referred to here are the same units with which motion is reported. For
+ * axes X and Y, the units are pixels.
* @param maxVelocity The maximum velocity that can be computed by this method.
* This value must be declared in the same unit as the units parameter. This value
* must be positive.
@@ -397,6 +416,45 @@ public final class VelocityTracker {
}
/**
+ * Retrieve the last computed velocity for a given motion axis. You must first call
+ * {@link #computeCurrentVelocity(int)} or {@link #computeCurrentVelocity(int, float)} before
+ * calling this function.
+ *
+ * <p>In addition to {@link MotionEvent#AXIS_X} and {@link MotionEvent#AXIS_Y} which have been
+ * supported since the introduction of this class, the following axes are supported for this
+ * method:
+ * <ul>
+ * <li> {@link MotionEvent#AXIS_SCROLL}: supported starting
+ * {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ * </ul>
+ *
+ * @param axis Which axis' velocity to return.
+ * @param id Which pointer's velocity to return.
+ * @return The previously computed velocity for {@code axis} for pointer ID of {@code id} if
+ * {@code axis} is supported for velocity tracking, or 0 if velocity tracking is not supported
+ * for the axis.
+ * @see #isAxisSupported(int)
+ */
+ public float getAxisVelocity(int axis, int id) {
+ return nativeGetVelocity(mPtr, axis, id);
+ }
+
+ /**
+ * Equivalent to calling {@link #getAxisVelocity(int, int)} for {@code axis} and the active
+ * pointer.
+ *
+ * @param axis Which axis' velocity to return.
+ * @return The previously computed velocity for {@code axis} for the active pointer if
+ * {@code axis} is supported for velocity tracking, or 0 if velocity tracking is not supported
+ * for the axis.
+ * @see #isAxisSupported(int)
+ * @see #getAxisVelocity(int, int)
+ */
+ public float getAxisVelocity(int axis) {
+ return nativeGetVelocity(mPtr, axis, ACTIVE_POINTER_ID);
+ }
+
+ /**
* Get an estimator for the movements of a pointer using past movements of the
* pointer to predict future movements.
*
@@ -426,8 +484,8 @@ public final class VelocityTracker {
* Past estimated positions are at negative times and future estimated positions
* are at positive times.
*
- * First coefficient is position (in pixels), second is velocity (in pixels per second),
- * third is acceleration (in pixels per second squared).
+ * First coefficient is position (in units), second is velocity (in units per second),
+ * third is acceleration (in units per second squared).
*
* @hide For internal use only. Not a final API.
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index efda257aed27..ff4588a7bc9b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -162,7 +162,6 @@ import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.InputDevice.InputSourceClass;
-import android.view.InsetsState.InternalInsetsType;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl.Transaction;
import android.view.View.AttachInfo;
@@ -1245,7 +1244,7 @@ public final class ViewRootImpl implements ViewParent,
final float[] sizeCompatScale = { 1f };
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
- mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
+ mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
mTempControls, attachedFrame, sizeCompatScale);
if (!attachedFrame.isValid()) {
attachedFrame = null;
@@ -1284,7 +1283,7 @@ public final class ViewRootImpl implements ViewParent,
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
- mInsetsController.getRequestedVisibilities(), 1f /* compactScale */,
+ mInsetsController.getRequestedVisibleTypes(), 1f /* compactScale */,
mTmpFrames);
setFrame(mTmpFrames.frame);
registerBackCallbackOnWindow();
@@ -2376,7 +2375,7 @@ public final class ViewRootImpl implements ViewParent,
mCompatibleVisibilityInfo.globalVisibility =
(mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
| (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
- dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
+ dispatchDispatchSystemUiVisibilityChanged();
if (mAttachInfo.mKeepScreenOn != oldScreenOn
|| mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
|| mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
@@ -2404,24 +2403,29 @@ public final class ViewRootImpl implements ViewParent,
/**
* Update the compatible system UI visibility for dispatching it to the legacy app.
- *
- * @param type Indicates which type of the insets source we are handling.
- * @param visible True if the insets source is visible.
- * @param hasControl True if we can control the insets source.
- */
- void updateCompatSysUiVisibility(@InternalInsetsType int type, boolean visible,
- boolean hasControl) {
- @InsetsType final int publicType = InsetsState.toPublicType(type);
- if (publicType != Type.statusBars() && publicType != Type.navigationBars()) {
- return;
- }
+ */
+ void updateCompatSysUiVisibility(@InsetsType int visibleTypes,
+ @InsetsType int requestedVisibleTypes, @InsetsType int controllableTypes) {
+ // If a type is controllable, the visibility is overridden by the requested visibility.
+ visibleTypes =
+ (requestedVisibleTypes & controllableTypes) | (visibleTypes & ~controllableTypes);
+
+ updateCompatSystemUiVisibilityInfo(SYSTEM_UI_FLAG_FULLSCREEN, Type.statusBars(),
+ visibleTypes, controllableTypes);
+ updateCompatSystemUiVisibilityInfo(SYSTEM_UI_FLAG_HIDE_NAVIGATION, Type.navigationBars(),
+ visibleTypes, controllableTypes);
+ dispatchDispatchSystemUiVisibilityChanged();
+ }
+
+ private void updateCompatSystemUiVisibilityInfo(int systemUiFlag, @InsetsType int insetsType,
+ @InsetsType int visibleTypes, @InsetsType int controllableTypes) {
final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
- final int systemUiFlag = publicType == Type.statusBars()
- ? View.SYSTEM_UI_FLAG_FULLSCREEN
- : View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
- if (visible) {
+ final boolean willBeVisible = (visibleTypes & insetsType) != 0;
+ final boolean hasControl = (controllableTypes & insetsType) != 0;
+ final boolean wasInvisible = (mAttachInfo.mSystemUiVisibility & systemUiFlag) != 0;
+ if (willBeVisible) {
info.globalVisibility &= ~systemUiFlag;
- if (hasControl && (mAttachInfo.mSystemUiVisibility & systemUiFlag) != 0) {
+ if (hasControl && wasInvisible) {
// The local system UI visibility can only be cleared while we have the control.
info.localChanges |= systemUiFlag;
}
@@ -2429,7 +2433,6 @@ public final class ViewRootImpl implements ViewParent,
info.globalVisibility |= systemUiFlag;
info.localChanges &= ~systemUiFlag;
}
- dispatchDispatchSystemUiVisibilityChanged(info);
}
/**
@@ -2445,25 +2448,28 @@ public final class ViewRootImpl implements ViewParent,
&& (info.globalVisibility & SYSTEM_UI_FLAG_LOW_PROFILE) != 0) {
info.globalVisibility &= ~SYSTEM_UI_FLAG_LOW_PROFILE;
info.localChanges |= SYSTEM_UI_FLAG_LOW_PROFILE;
- dispatchDispatchSystemUiVisibilityChanged(info);
+ dispatchDispatchSystemUiVisibilityChanged();
}
}
- private void dispatchDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
- if (mDispatchedSystemUiVisibility != args.globalVisibility) {
+ private void dispatchDispatchSystemUiVisibilityChanged() {
+ if (mDispatchedSystemUiVisibility != mCompatibleVisibilityInfo.globalVisibility) {
mHandler.removeMessages(MSG_DISPATCH_SYSTEM_UI_VISIBILITY);
- mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY, args));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_DISPATCH_SYSTEM_UI_VISIBILITY));
}
}
- private void handleDispatchSystemUiVisibilityChanged(SystemUiVisibilityInfo args) {
- if (mView == null) return;
- if (args.localChanges != 0) {
- mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges);
- args.localChanges = 0;
+ private void handleDispatchSystemUiVisibilityChanged() {
+ if (mView == null) {
+ return;
+ }
+ final SystemUiVisibilityInfo info = mCompatibleVisibilityInfo;
+ if (info.localChanges != 0) {
+ mView.updateLocalSystemUiVisibility(info.localValue, info.localChanges);
+ info.localChanges = 0;
}
- final int visibility = args.globalVisibility & View.SYSTEM_UI_CLEARABLE_FLAGS;
+ final int visibility = info.globalVisibility & View.SYSTEM_UI_CLEARABLE_FLAGS;
if (mDispatchedSystemUiVisibility != visibility) {
mDispatchedSystemUiVisibility = visibility;
mView.dispatchSystemUiVisibilityChanged(visibility);
@@ -4964,7 +4970,7 @@ public final class ViewRootImpl implements ViewParent,
}
void reportKeepClearAreasChanged() {
- if (!mHasPendingKeepClearAreaChange) {
+ if (!mHasPendingKeepClearAreaChange || mView == null) {
return;
}
mHasPendingKeepClearAreaChange = false;
@@ -5728,7 +5734,7 @@ public final class ViewRootImpl implements ViewParent,
handleDragEvent(event);
} break;
case MSG_DISPATCH_SYSTEM_UI_VISIBILITY: {
- handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj);
+ handleDispatchSystemUiVisibilityChanged();
} break;
case MSG_UPDATE_CONFIGURATION: {
Configuration config = (Configuration) msg.obj;
@@ -8148,7 +8154,7 @@ public final class ViewRootImpl implements ViewParent,
state.getDisplayCutoutSafe(displayCutoutSafe);
mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
- measuredWidth, measuredHeight, mInsetsController.getRequestedVisibilities(),
+ measuredWidth, measuredHeight, mInsetsController.getRequestedVisibleTypes(),
1f /* compatScale */, mTmpFrames);
mWinFrameInScreen.set(mTmpFrames.frame);
if (mTranslator != null) {
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index d960ba1489ca..c59d83ec4c6e 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -145,15 +145,17 @@ public class ViewRootInsetsControllerHost implements InsetsController.Host {
}
@Override
- public void updateCompatSysUiVisibility(int type, boolean visible, boolean hasControl) {
- mViewRoot.updateCompatSysUiVisibility(type, visible, hasControl);
+ public void updateCompatSysUiVisibility(int visibleTypes, int requestedVisibleTypes,
+ int controllableTypes) {
+ mViewRoot.updateCompatSysUiVisibility(visibleTypes, requestedVisibleTypes,
+ controllableTypes);
}
@Override
- public void updateRequestedVisibilities(InsetsVisibilities vis) {
+ public void updateRequestedVisibleTypes(@WindowInsets.Type.InsetsType int types) {
try {
if (mViewRoot.mAdded) {
- mViewRoot.mWindowSession.updateRequestedVisibilities(mViewRoot.mWindow, vis);
+ mViewRoot.mWindowSession.updateRequestedVisibleTypes(mViewRoot.mWindow, types);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to call insetsModified", e);
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index c1dddbee408f..2a76c4e0a694 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1429,6 +1429,8 @@ public final class WindowInsets {
static final int LAST = GENERIC_OVERLAYS;
static final int SIZE = 10;
+ static final int DEFAULT_VISIBLE = ~IME;
+
static int indexOf(@InsetsType int type) {
switch (type) {
case STATUS_BARS:
@@ -1457,7 +1459,8 @@ public final class WindowInsets {
}
}
- static String toString(@InsetsType int types) {
+ /** @hide */
+ public static String toString(@InsetsType int types) {
StringBuilder result = new StringBuilder();
if ((types & STATUS_BARS) != 0) {
result.append("statusBars |");
@@ -1598,6 +1601,15 @@ public final class WindowInsets {
}
/**
+ * @return Default visible types.
+ *
+ * @hide
+ */
+ public static @InsetsType int defaultVisible() {
+ return DEFAULT_VISIBLE;
+ }
+
+ /**
* @return All inset types combined.
*
* @hide
@@ -1605,6 +1617,15 @@ public final class WindowInsets {
public static @InsetsType int all() {
return 0xFFFFFFFF;
}
+
+ /**
+ * @return System bars which can be controlled by {@link View.SystemUiVisibility}.
+ *
+ * @hide
+ */
+ public static boolean hasCompatSystemBars(@InsetsType int types) {
+ return (types & (STATUS_BARS | NAVIGATION_BARS)) != 0;
+ }
}
/**
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 63f9e13214ff..bc0bab7b5e95 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -23,7 +23,6 @@ import android.graphics.Insets;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.CancellationSignal;
-import android.view.InsetsState.InternalInsetsType;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
@@ -279,11 +278,10 @@ public interface WindowInsetsController {
InsetsState getState();
/**
- * @return Whether the specified insets source is currently requested to be visible by the
- * application.
+ * @return Insets types that have been requested to be visible.
* @hide
*/
- boolean isRequestedVisible(@InternalInsetsType int type);
+ @InsetsType int getRequestedVisibleTypes();
/**
* Adds a {@link OnControllableInsetsChangedListener} to the window insets controller.
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 5ed9d2f90a72..70778046d9f5 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -40,6 +40,7 @@ import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
/**
@@ -63,7 +64,7 @@ public class WindowLayout {
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
- int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
+ int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
float compatScale, ClientWindowFrames frames) {
final int type = attrs.type;
final int fl = attrs.flags;
@@ -130,7 +131,7 @@ public class WindowLayout {
&& (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
final Insets systemBarsInsets = state.calculateInsets(
- displayFrame, WindowInsets.Type.systemBars(), requestedVisibilities);
+ displayFrame, WindowInsets.Type.systemBars(), requestedVisibleTypes);
if (systemBarsInsets.left > 0) {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
@@ -288,7 +289,7 @@ public class WindowLayout {
+ " displayCutoutSafe=" + displayCutoutSafe
+ " attrs=" + attrs
+ " state=" + state
- + " requestedVisibilities=" + requestedVisibilities);
+ + " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes));
}
public static void extendFrameByCutout(Rect displayCutoutSafe,
diff --git a/core/java/android/view/WindowlessWindowLayout.java b/core/java/android/view/WindowlessWindowLayout.java
index 5bec5b6b6722..8ef4d7874f02 100644
--- a/core/java/android/view/WindowlessWindowLayout.java
+++ b/core/java/android/view/WindowlessWindowLayout.java
@@ -18,6 +18,7 @@ package android.view;
import android.app.WindowConfiguration.WindowingMode;
import android.graphics.Rect;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
/**
@@ -29,7 +30,7 @@ public class WindowlessWindowLayout extends WindowLayout {
@Override
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
- int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
+ int requestedWidth, int requestedHeight, @InsetsType int requestedVisibleTypes,
float compatScale, ClientWindowFrames frames) {
frames.frame.set(0, 0, attrs.width, attrs.height);
frames.displayFrame.set(frames.frame);
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index fbf7456ac05d..69340aa39daf 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -30,6 +30,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Log;
import android.util.MergedConfiguration;
+import android.view.WindowInsets.Type.InsetsType;
import android.window.ClientWindowFrames;
import android.window.OnBackInvokedCallbackInfo;
@@ -147,7 +148,7 @@ public class WindowlessWindowManager implements IWindowSession {
*/
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
@@ -198,11 +199,11 @@ public class WindowlessWindowManager implements IWindowSession {
*/
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
- return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
+ return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibleTypes,
outInputChannel, outInsetsState, outActiveControls, outAttachedFrame,
outSizeCompatScale);
}
@@ -491,7 +492,8 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) {
+ public void updateRequestedVisibleTypes(IWindow window,
+ @InsetsType int requestedVisibleTypes) {
}
@Override
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index f0fe573d20e0..e32c6aafc4df 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.internal.inputmethod;
+package android.view.inputmethod;
import android.annotation.AnyThread;
import android.annotation.NonNull;
@@ -36,8 +36,11 @@ import java.util.function.Consumer;
*
* <p>All public methods are guaranteed to do nothing when {@link IInputMethodManager} is
* unavailable.</p>
+ *
+ * <p>If you want to use any of this method outside of {@code android.view.inputmethod}, create
+ * a wrapper method in {@link InputMethodManagerGlobal} instead of making this class public.</p>
*/
-public final class IInputMethodManagerGlobal {
+final class IInputMethodManagerGlobalInvoker {
@Nullable
private static volatile IInputMethodManager sServiceCache = null;
@@ -45,7 +48,7 @@ public final class IInputMethodManagerGlobal {
* @return {@code true} if {@link IInputMethodManager} is available.
*/
@AnyThread
- public static boolean isAvailable() {
+ static boolean isAvailable() {
return getService() != null;
}
@@ -79,14 +82,14 @@ public final class IInputMethodManagerGlobal {
*
* @param protoDump client or service side information to be stored by the server
* @param source where the information is coming from, refer to
- * {@link ImeTracing#IME_TRACING_FROM_CLIENT} and
- * {@link ImeTracing#IME_TRACING_FROM_IMS}
+ * {@link com.android.internal.inputmethod.ImeTracing#IME_TRACING_FROM_CLIENT} and
+ * {@link com.android.internal.inputmethod.ImeTracing#IME_TRACING_FROM_IMS}
* @param where where the information is coming from.
* @param exceptionHandler an optional {@link RemoteException} handler.
*/
@RequiresNoPermission
@AnyThread
- public static void startProtoDump(byte[] protoDump, int source, String where,
+ static void startProtoDump(byte[] protoDump, int source, String where,
@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -106,7 +109,7 @@ public final class IInputMethodManagerGlobal {
*/
@RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
@AnyThread
- public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+ static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
if (service == null) {
return;
@@ -125,7 +128,7 @@ public final class IInputMethodManagerGlobal {
*/
@RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
@AnyThread
- public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+ static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
if (service == null) {
return;
@@ -144,7 +147,7 @@ public final class IInputMethodManagerGlobal {
*/
@RequiresNoPermission
@AnyThread
- public static boolean isImeTraceEnabled() {
+ static boolean isImeTraceEnabled() {
final IInputMethodManager service = getService();
if (service == null) {
return false;
@@ -155,4 +158,21 @@ public final class IInputMethodManagerGlobal {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Invokes {@link IInputMethodManager#removeImeSurface()}
+ */
+ @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @AnyThread
+ static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
+ final IInputMethodManager service = getService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.removeImeSurface();
+ } catch (RemoteException e) {
+ handleRemoteExceptionOrRethrow(e, exceptionHandler);
+ }
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
new file mode 100644
index 000000000000..63d9167bb2f9
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
@@ -0,0 +1,104 @@
+/*
+ * 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.view.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.Nullable;
+import android.annotation.RequiresNoPermission;
+import android.annotation.RequiresPermission;
+import android.os.RemoteException;
+
+import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.view.IInputMethodManager;
+
+import java.util.function.Consumer;
+
+/**
+ * Defines a set of static methods that can be used globally by framework classes.
+ *
+ * @hide
+ */
+public class InputMethodManagerGlobal {
+ /**
+ * @return {@code true} if IME tracing is currently is available.
+ */
+ @AnyThread
+ public static boolean isImeTraceAvailable() {
+ return IInputMethodManagerGlobalInvoker.isAvailable();
+ }
+
+ /**
+ * Invokes {@link IInputMethodManager#startProtoDump(byte[], int, String)}.
+ *
+ * @param protoDump client or service side information to be stored by the server
+ * @param source where the information is coming from, refer to
+ * {@link ImeTracing#IME_TRACING_FROM_CLIENT} and
+ * {@link ImeTracing#IME_TRACING_FROM_IMS}
+ * @param where where the information is coming from.
+ * @param exceptionHandler an optional {@link RemoteException} handler.
+ */
+ @RequiresNoPermission
+ @AnyThread
+ public static void startProtoDump(byte[] protoDump, int source, String where,
+ @Nullable Consumer<RemoteException> exceptionHandler) {
+ IInputMethodManagerGlobalInvoker.startProtoDump(protoDump, source, where, exceptionHandler);
+ }
+
+ /**
+ * Invokes {@link IInputMethodManager#startImeTrace()}.
+ *
+ * @param exceptionHandler an optional {@link RemoteException} handler.
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
+ @AnyThread
+ public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+ IInputMethodManagerGlobalInvoker.startImeTrace(exceptionHandler);
+ }
+
+ /**
+ * Invokes {@link IInputMethodManager#stopImeTrace()}.
+ *
+ * @param exceptionHandler an optional {@link RemoteException} handler.
+ */
+ @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
+ @AnyThread
+ public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+ IInputMethodManagerGlobalInvoker.stopImeTrace(exceptionHandler);
+ }
+
+ /**
+ * Invokes {@link IInputMethodManager#isImeTraceEnabled()}.
+ *
+ * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
+ */
+ @RequiresNoPermission
+ @AnyThread
+ public static boolean isImeTraceEnabled() {
+ return IInputMethodManagerGlobalInvoker.isImeTraceEnabled();
+ }
+
+ /**
+ * Invokes {@link IInputMethodManager#removeImeSurface()}
+ *
+ * @param exceptionHandler an optional {@link RemoteException} handler.
+ */
+ @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @AnyThread
+ public static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
+ IInputMethodManagerGlobalInvoker.removeImeSurface(exceptionHandler);
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index 121839bc9ea6..bdfcb0328632 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -23,6 +24,7 @@ import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.icu.text.DisplayContext;
import android.icu.text.LocaleDisplayNames;
+import android.icu.util.ULocale;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -74,6 +76,10 @@ public final class InputMethodSubtype implements Parcelable {
/** {@hide} */
public static final int SUBTYPE_ID_NONE = 0;
+ private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+
+ private static final String UNDEFINED_LANGUAGE_TAG = "und";
+
private final boolean mIsAuxiliary;
private final boolean mOverridesImplicitlyEnabledSubtype;
private final boolean mIsAsciiCapable;
@@ -90,6 +96,14 @@ public final class InputMethodSubtype implements Parcelable {
private volatile HashMap<String, String> mExtraValueHashMapCache;
/**
+ * A volatile cache to optimize {@link #getCanonicalizedLanguageTag()}.
+ *
+ * <p>{@code null} means that the initial evaluation is not yet done.</p>
+ */
+ @Nullable
+ private volatile String mCachedCanonicalizedLanguageTag;
+
+ /**
* InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
* This class is designed to be used with
* {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
@@ -392,6 +406,65 @@ public final class InputMethodSubtype implements Parcelable {
}
/**
+ * Returns a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}.
+ *
+ * <p>This has an internal cache mechanism. Subsequent calls are in general cheap and fast.</p>
+ *
+ * @return a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}. An
+ * empty string if {@link #getLocaleObject()} returns {@code null} or an empty
+ * {@link Locale} object.
+ * @hide
+ */
+ @AnyThread
+ @NonNull
+ public String getCanonicalizedLanguageTag() {
+ final String cachedValue = mCachedCanonicalizedLanguageTag;
+ if (cachedValue != null) {
+ return cachedValue;
+ }
+
+ String result = null;
+ final Locale locale = getLocaleObject();
+ if (locale != null) {
+ final String langTag = locale.toLanguageTag();
+ if (!TextUtils.isEmpty(langTag)) {
+ result = ULocale.createCanonical(ULocale.forLanguageTag(langTag)).toLanguageTag();
+ }
+ }
+ result = TextUtils.emptyIfNull(result);
+ mCachedCanonicalizedLanguageTag = result;
+ return result;
+ }
+
+ /**
+ * Determines whether this {@link InputMethodSubtype} can be used as the key of mapping rules
+ * between {@link InputMethodSubtype} and hardware keyboard layout.
+ *
+ * <p>Note that in a future build may require different rules. Design the system so that the
+ * system can automatically take care of any rule changes upon OTAs.</p>
+ *
+ * @return {@code true} if this {@link InputMethodSubtype} can be used as the key of mapping
+ * rules between {@link InputMethodSubtype} and hardware keyboard layout.
+ * @hide
+ */
+ public boolean isSuitableForPhysicalKeyboardLayoutMapping() {
+ if (hashCode() == SUBTYPE_ID_NONE) {
+ return false;
+ }
+ if (!TextUtils.equals(getMode(), SUBTYPE_MODE_KEYBOARD)) {
+ return false;
+ }
+ if (isAuxiliary()) {
+ return false;
+ }
+ final String langTag = getCanonicalizedLanguageTag();
+ if (langTag.isEmpty() || TextUtils.equals(langTag, UNDEFINED_LANGUAGE_TAG)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* @return The mode of the subtype.
*/
public String getMode() {
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index d16103767987..1b64e613ed66 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -25,7 +25,8 @@ import android.content.pm.ActivityInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
/**
@@ -181,11 +182,7 @@ public final class StartingWindowInfo implements Parcelable {
*/
public TaskSnapshot taskSnapshot;
- /**
- * The requested insets visibility of the top main window.
- * @hide
- */
- public final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ public @InsetsType int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
public StartingWindowInfo() {
@@ -218,7 +215,7 @@ public final class StartingWindowInfo implements Parcelable {
dest.writeInt(splashScreenThemeResId);
dest.writeBoolean(isKeyguardOccluded);
dest.writeTypedObject(taskSnapshot, flags);
- requestedVisibilities.writeToParcel(dest, flags);
+ dest.writeInt(requestedVisibleTypes);
}
void readFromParcel(@NonNull Parcel source) {
@@ -232,7 +229,7 @@ public final class StartingWindowInfo implements Parcelable {
splashScreenThemeResId = source.readInt();
isKeyguardOccluded = source.readBoolean();
taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
- requestedVisibilities.readFromParcel(source);
+ requestedVisibleTypes = source.readInt();
}
@Override
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index a4328cc14aa0..e6a9b543dae2 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -22,6 +22,7 @@ import android.app.ActivityThread;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodManagerGlobal;
import java.io.PrintWriter;
@@ -44,7 +45,7 @@ public abstract class ImeTracing {
private static ImeTracing sInstance;
static boolean sEnabled = false;
- private final boolean mIsAvailable = IInputMethodManagerGlobal.isAvailable();
+ private final boolean mIsAvailable = InputMethodManagerGlobal.isImeTraceAvailable();
protected boolean mDumpInProgress;
protected final Object mDumpInProgressLock = new Object();
@@ -81,7 +82,7 @@ public abstract class ImeTracing {
* @param where
*/
public void sendToService(byte[] protoDump, int source, String where) {
- IInputMethodManagerGlobal.startProtoDump(protoDump, source, where,
+ InputMethodManagerGlobal.startProtoDump(protoDump, source, where,
e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
}
@@ -90,7 +91,7 @@ public abstract class ImeTracing {
*/
@RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
public final void startImeTrace() {
- IInputMethodManagerGlobal.startImeTrace(e -> Log.e(TAG, "Could not start ime trace.", e));
+ InputMethodManagerGlobal.startImeTrace(e -> Log.e(TAG, "Could not start ime trace.", e));
}
/**
@@ -98,7 +99,7 @@ public abstract class ImeTracing {
*/
@RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
public final void stopImeTrace() {
- IInputMethodManagerGlobal.stopImeTrace(e -> Log.e(TAG, "Could not stop ime trace.", e));
+ InputMethodManagerGlobal.stopImeTrace(e -> Log.e(TAG, "Could not stop ime trace.", e));
}
/**
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
index 4caca84d76c7..95ed4ed50971 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodManagerGlobal;
import java.io.PrintWriter;
@@ -28,7 +29,7 @@ import java.io.PrintWriter;
*/
class ImeTracingClientImpl extends ImeTracing {
ImeTracingClientImpl() {
- sEnabled = IInputMethodManagerGlobal.isImeTraceEnabled();
+ sEnabled = InputMethodManagerGlobal.isImeTraceEnabled();
}
@Override
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index a352063aa6ee..3e988e61d978 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -20,8 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.clearsCompatInsets;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
@@ -79,8 +77,6 @@ import android.view.ActionMode;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.InputQueue;
-import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.LayoutInflater;
@@ -98,6 +94,7 @@ import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowCallbacks;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager;
@@ -145,13 +142,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
new ColorViewAttributes(FLAG_TRANSLUCENT_STATUS,
Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.statusBarBackground, ITYPE_STATUS_BAR);
+ com.android.internal.R.id.statusBarBackground,
+ WindowInsets.Type.statusBars());
public static final ColorViewAttributes NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES =
new ColorViewAttributes(FLAG_TRANSLUCENT_NAVIGATION,
Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
- com.android.internal.R.id.navigationBarBackground, ITYPE_NAVIGATION_BAR);
+ com.android.internal.R.id.navigationBarBackground,
+ WindowInsets.Type.navigationBars());
// This is used to workaround an issue where the PiP shadow can be transparent if the window
// background is transparent
@@ -1106,6 +1105,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
final WindowInsetsController controller = getWindowInsetsController();
+ final @InsetsType int requestedVisibleTypes = controller.getRequestedVisibleTypes();
// IME is an exceptional floating window that requires color view.
final boolean isImeWindow =
@@ -1164,7 +1164,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mWindow.mNavigationBarDividerColor, navBarSize,
navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
0 /* sideInset */, animate && !disallowAnimate,
- mForceWindowDrawsBarBackgrounds, controller);
+ mForceWindowDrawsBarBackgrounds, requestedVisibleTypes);
boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
mDrawLegacyNavigationBarBackground =
(mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
@@ -1187,7 +1187,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
updateColorViewInt(mStatusColorViewState, statusBarColor, 0,
mLastTopInset, false /* matchVertical */, statusBarNeedsLeftInset,
statusBarSideInset, animate && !disallowAnimate,
- mForceWindowDrawsBarBackgrounds, controller);
+ mForceWindowDrawsBarBackgrounds, requestedVisibleTypes);
if (mHasCaption) {
mDecorCaptionView.getCaption().setBackgroundColor(statusBarColor);
@@ -1206,7 +1206,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
// Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer
// consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
- || !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR));
+ || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
boolean forceConsumingNavBar =
((mForceWindowDrawsBarBackgrounds || mDrawLegacyNavigationBarBackgroundHandled)
@@ -1226,10 +1226,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
// If we didn't request fullscreen layout, but we still got it because of the
// mForceWindowDrawsBarBackgrounds flag, also consume top inset.
// If we should always consume system bars, only consume that if the app wanted to go to
- // fullscreen, as othrewise we can expect the app to handle it.
+ // fullscreen, as otherwise we can expect the app to handle it.
boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
|| (attrs.flags & FLAG_FULLSCREEN) != 0
- || !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR));
+ || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0;
boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
&& decorFitsSystemWindows
&& (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
@@ -1438,10 +1438,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
*/
private void updateColorViewInt(final ColorViewState state, int color, int dividerColor,
int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
- boolean force, WindowInsetsController controller) {
+ boolean force, @InsetsType int requestedVisibleTypes) {
state.present = state.attributes.isPresent(
- (controller.isRequestedVisible(state.attributes.insetsType)
- || mLastShouldAlwaysConsumeSystemBars),
+ (requestedVisibleTypes & state.attributes.insetsType) != 0
+ || mLastShouldAlwaysConsumeSystemBars,
mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
@@ -2686,11 +2686,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
final int horizontalGravity;
final int seascapeGravity;
final String transitionName;
- final @InternalInsetsType int insetsType;
+ final @InsetsType int insetsType;
private ColorViewAttributes(int translucentFlag, int verticalGravity, int horizontalGravity,
- int seascapeGravity, String transitionName, int id,
- @InternalInsetsType int insetsType) {
+ int seascapeGravity, String transitionName, int id, @InsetsType int insetsType) {
this.id = id;
this.translucentFlag = translucentFlag;
this.verticalGravity = verticalGravity;
@@ -2707,13 +2706,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
public boolean isVisible(boolean present, int color, int windowFlags, boolean force) {
return present
- && (color & Color.BLACK) != 0
- && ((windowFlags & translucentFlag) == 0 || force);
+ && Color.alpha(color) != 0
+ && ((windowFlags & translucentFlag) == 0 || force);
}
- public boolean isVisible(InsetsState state, int color, int windowFlags, boolean force) {
- final boolean present = isPresent(state.getSource(insetsType).isVisible(), windowFlags,
- force);
+ public boolean isVisible(@InsetsType int requestedVisibleTypes, int color, int windowFlags,
+ boolean force) {
+ final boolean requestedVisible = (requestedVisibleTypes & insetsType) != 0;
+ final boolean present = isPresent(requestedVisible, windowFlags, force);
return isVisible(present, color, windowFlags, force);
}
}
diff --git a/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java b/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java
index 5de69a6310b9..c56bc493892a 100644
--- a/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java
+++ b/core/java/com/android/internal/util/ArtBinaryXmlPullParser.java
@@ -18,6 +18,9 @@ package com.android.internal.util;
import android.annotation.NonNull;
+import com.android.modules.utils.BinaryXmlPullParser;
+import com.android.modules.utils.FastDataInput;
+
import java.io.DataInput;
import java.io.InputStream;
diff --git a/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java b/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java
index cd534c96ca9d..98a213504ae6 100644
--- a/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java
+++ b/core/java/com/android/internal/util/ArtBinaryXmlSerializer.java
@@ -18,6 +18,9 @@ package com.android.internal.util;
import android.annotation.NonNull;
+import com.android.modules.utils.BinaryXmlSerializer;
+import com.android.modules.utils.FastDataOutput;
+
import java.io.DataOutput;
import java.io.OutputStream;
diff --git a/core/java/com/android/internal/util/ArtFastDataInput.java b/core/java/com/android/internal/util/ArtFastDataInput.java
index 25490215c146..3e8916caead9 100644
--- a/core/java/com/android/internal/util/ArtFastDataInput.java
+++ b/core/java/com/android/internal/util/ArtFastDataInput.java
@@ -19,6 +19,8 @@ package com.android.internal.util;
import android.annotation.NonNull;
import android.util.CharsetUtils;
+import com.android.modules.utils.FastDataInput;
+
import java.io.DataInput;
import java.io.IOException;
import java.io.InputStream;
diff --git a/core/java/com/android/internal/util/ArtFastDataOutput.java b/core/java/com/android/internal/util/ArtFastDataOutput.java
index de9e93bea7f1..ac595b6fd151 100644
--- a/core/java/com/android/internal/util/ArtFastDataOutput.java
+++ b/core/java/com/android/internal/util/ArtFastDataOutput.java
@@ -19,6 +19,8 @@ package com.android.internal.util;
import android.annotation.NonNull;
import android.util.CharsetUtils;
+import com.android.modules.utils.FastDataOutput;
+
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
diff --git a/core/java/com/android/internal/util/BinaryXmlPullParser.java b/core/java/com/android/internal/util/BinaryXmlPullParser.java
deleted file mode 100644
index a70c10efc84f..000000000000
--- a/core/java/com/android/internal/util/BinaryXmlPullParser.java
+++ /dev/null
@@ -1,945 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import static com.android.internal.util.BinaryXmlSerializer.ATTRIBUTE;
-import static com.android.internal.util.BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_FALSE;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_TRUE;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_BASE64;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_HEX;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_DOUBLE;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_FLOAT;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT_HEX;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG_HEX;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_NULL;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING;
-import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING_INTERNED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.text.TextUtils;
-import android.util.Base64;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Parser that reads XML documents using a custom binary wire protocol which
- * benchmarking has shown to be 8.5x faster than {@link Xml.newFastPullParser()}
- * for a typical {@code packages.xml}.
- * <p>
- * The high-level design of the wire protocol is to directly serialize the event
- * stream, while efficiently and compactly writing strongly-typed primitives
- * delivered through the {@link TypedXmlSerializer} interface.
- * <p>
- * Each serialized event is a single byte where the lower half is a normal
- * {@link XmlPullParser} token and the upper half is an optional data type
- * signal, such as {@link #TYPE_INT}.
- * <p>
- * This parser has some specific limitations:
- * <ul>
- * <li>Only the UTF-8 encoding is supported.
- * <li>Variable length values, such as {@code byte[]} or {@link String}, are
- * limited to 65,535 bytes in length. Note that {@link String} values are stored
- * as UTF-8 on the wire.
- * <li>Namespaces, prefixes, properties, and options are unsupported.
- * </ul>
- */
-public class BinaryXmlPullParser implements TypedXmlPullParser {
- private FastDataInput mIn;
-
- private int mCurrentToken = START_DOCUMENT;
- private int mCurrentDepth = 0;
- private String mCurrentName;
- private String mCurrentText;
-
- /**
- * Pool of attributes parsed for the currently tag. All interactions should
- * be done via {@link #obtainAttribute()}, {@link #findAttribute(String)},
- * and {@link #resetAttributes()}.
- */
- private int mAttributeCount = 0;
- private Attribute[] mAttributes;
-
- @Override
- public void setInput(InputStream is, String encoding) throws XmlPullParserException {
- if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
- throw new UnsupportedOperationException();
- }
-
- if (mIn != null) {
- mIn.release();
- mIn = null;
- }
-
- mIn = obtainFastDataInput(is);
-
- mCurrentToken = START_DOCUMENT;
- mCurrentDepth = 0;
- mCurrentName = null;
- mCurrentText = null;
-
- mAttributeCount = 0;
- mAttributes = new Attribute[8];
- for (int i = 0; i < mAttributes.length; i++) {
- mAttributes[i] = new Attribute();
- }
-
- try {
- final byte[] magic = new byte[4];
- mIn.readFully(magic);
- if (!Arrays.equals(magic, PROTOCOL_MAGIC_VERSION_0)) {
- throw new IOException("Unexpected magic " + bytesToHexString(magic));
- }
-
- // We're willing to immediately consume a START_DOCUMENT if present,
- // but we're okay if it's missing
- if (peekNextExternalToken() == START_DOCUMENT) {
- consumeToken();
- }
- } catch (IOException e) {
- throw new XmlPullParserException(e.toString());
- }
- }
-
- @NonNull
- protected FastDataInput obtainFastDataInput(@NonNull InputStream is) {
- return FastDataInput.obtain(is);
- }
-
- @Override
- public void setInput(Reader in) throws XmlPullParserException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int next() throws XmlPullParserException, IOException {
- while (true) {
- final int token = nextToken();
- switch (token) {
- case START_TAG:
- case END_TAG:
- case END_DOCUMENT:
- return token;
- case TEXT:
- consumeAdditionalText();
- // Per interface docs, empty text regions are skipped
- if (mCurrentText == null || mCurrentText.length() == 0) {
- continue;
- } else {
- return TEXT;
- }
- }
- }
- }
-
- @Override
- public int nextToken() throws XmlPullParserException, IOException {
- if (mCurrentToken == XmlPullParser.END_TAG) {
- mCurrentDepth--;
- }
-
- int token;
- try {
- token = peekNextExternalToken();
- consumeToken();
- } catch (EOFException e) {
- token = END_DOCUMENT;
- }
- switch (token) {
- case XmlPullParser.START_TAG:
- // We need to peek forward to find the next external token so
- // that we parse all pending INTERNAL_ATTRIBUTE tokens
- peekNextExternalToken();
- mCurrentDepth++;
- break;
- }
- mCurrentToken = token;
- return token;
- }
-
- /**
- * Peek at the next "external" token without consuming it.
- * <p>
- * External tokens, such as {@link #START_TAG}, are expected by typical
- * {@link XmlPullParser} clients. In contrast, internal tokens, such as
- * {@link #ATTRIBUTE}, are not expected by typical clients.
- * <p>
- * This method consumes any internal events until it reaches the next
- * external event.
- */
- private int peekNextExternalToken() throws IOException, XmlPullParserException {
- while (true) {
- final int token = peekNextToken();
- switch (token) {
- case ATTRIBUTE:
- consumeToken();
- continue;
- default:
- return token;
- }
- }
- }
-
- /**
- * Peek at the next token in the underlying stream without consuming it.
- */
- private int peekNextToken() throws IOException {
- return mIn.peekByte() & 0x0f;
- }
-
- /**
- * Parse and consume the next token in the underlying stream.
- */
- private void consumeToken() throws IOException, XmlPullParserException {
- final int event = mIn.readByte();
- final int token = event & 0x0f;
- final int type = event & 0xf0;
- switch (token) {
- case ATTRIBUTE: {
- final Attribute attr = obtainAttribute();
- attr.name = mIn.readInternedUTF();
- attr.type = type;
- switch (type) {
- case TYPE_NULL:
- case TYPE_BOOLEAN_TRUE:
- case TYPE_BOOLEAN_FALSE:
- // Nothing extra to fill in
- break;
- case TYPE_STRING:
- attr.valueString = mIn.readUTF();
- break;
- case TYPE_STRING_INTERNED:
- attr.valueString = mIn.readInternedUTF();
- break;
- case TYPE_BYTES_HEX:
- case TYPE_BYTES_BASE64:
- final int len = mIn.readUnsignedShort();
- final byte[] res = new byte[len];
- mIn.readFully(res);
- attr.valueBytes = res;
- break;
- case TYPE_INT:
- case TYPE_INT_HEX:
- attr.valueInt = mIn.readInt();
- break;
- case TYPE_LONG:
- case TYPE_LONG_HEX:
- attr.valueLong = mIn.readLong();
- break;
- case TYPE_FLOAT:
- attr.valueFloat = mIn.readFloat();
- break;
- case TYPE_DOUBLE:
- attr.valueDouble = mIn.readDouble();
- break;
- default:
- throw new IOException("Unexpected data type " + type);
- }
- break;
- }
- case XmlPullParser.START_DOCUMENT: {
- mCurrentName = null;
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.END_DOCUMENT: {
- mCurrentName = null;
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.START_TAG: {
- mCurrentName = mIn.readInternedUTF();
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.END_TAG: {
- mCurrentName = mIn.readInternedUTF();
- mCurrentText = null;
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.TEXT:
- case XmlPullParser.CDSECT:
- case XmlPullParser.PROCESSING_INSTRUCTION:
- case XmlPullParser.COMMENT:
- case XmlPullParser.DOCDECL:
- case XmlPullParser.IGNORABLE_WHITESPACE: {
- mCurrentName = null;
- mCurrentText = mIn.readUTF();
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- case XmlPullParser.ENTITY_REF: {
- mCurrentName = mIn.readUTF();
- mCurrentText = resolveEntity(mCurrentName);
- if (mAttributeCount > 0) resetAttributes();
- break;
- }
- default: {
- throw new IOException("Unknown token " + token + " with type " + type);
- }
- }
- }
-
- /**
- * When the current tag is {@link #TEXT}, consume all subsequent "text"
- * events, as described by {@link #next}. When finished, the current event
- * will still be {@link #TEXT}.
- */
- private void consumeAdditionalText() throws IOException, XmlPullParserException {
- String combinedText = mCurrentText;
- while (true) {
- final int token = peekNextExternalToken();
- switch (token) {
- case COMMENT:
- case PROCESSING_INSTRUCTION:
- // Quietly consumed
- consumeToken();
- break;
- case TEXT:
- case CDSECT:
- case ENTITY_REF:
- // Additional text regions collected
- consumeToken();
- combinedText += mCurrentText;
- break;
- default:
- // Next token is something non-text, so wrap things up
- mCurrentToken = TEXT;
- mCurrentName = null;
- mCurrentText = combinedText;
- return;
- }
- }
- }
-
- static @NonNull String resolveEntity(@NonNull String entity)
- throws XmlPullParserException {
- switch (entity) {
- case "lt": return "<";
- case "gt": return ">";
- case "amp": return "&";
- case "apos": return "'";
- case "quot": return "\"";
- }
- if (entity.length() > 1 && entity.charAt(0) == '#') {
- final char c = (char) Integer.parseInt(entity.substring(1));
- return new String(new char[] { c });
- }
- throw new XmlPullParserException("Unknown entity " + entity);
- }
-
- @Override
- public void require(int type, String namespace, String name)
- throws XmlPullParserException, IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- if (mCurrentToken != type || !Objects.equals(mCurrentName, name)) {
- throw new XmlPullParserException(getPositionDescription());
- }
- }
-
- @Override
- public String nextText() throws XmlPullParserException, IOException {
- if (getEventType() != START_TAG) {
- throw new XmlPullParserException(getPositionDescription());
- }
- int eventType = next();
- if (eventType == TEXT) {
- String result = getText();
- eventType = next();
- if (eventType != END_TAG) {
- throw new XmlPullParserException(getPositionDescription());
- }
- return result;
- } else if (eventType == END_TAG) {
- return "";
- } else {
- throw new XmlPullParserException(getPositionDescription());
- }
- }
-
- @Override
- public int nextTag() throws XmlPullParserException, IOException {
- int eventType = next();
- if (eventType == TEXT && isWhitespace()) {
- eventType = next();
- }
- if (eventType != START_TAG && eventType != END_TAG) {
- throw new XmlPullParserException(getPositionDescription());
- }
- return eventType;
- }
-
- /**
- * Allocate and return a new {@link Attribute} associated with the tag being
- * currently processed. This will automatically grow the internal pool as
- * needed.
- */
- private @NonNull Attribute obtainAttribute() {
- if (mAttributeCount == mAttributes.length) {
- final int before = mAttributes.length;
- final int after = before + (before >> 1);
- mAttributes = Arrays.copyOf(mAttributes, after);
- for (int i = before; i < after; i++) {
- mAttributes[i] = new Attribute();
- }
- }
- return mAttributes[mAttributeCount++];
- }
-
- /**
- * Clear any {@link Attribute} instances that have been allocated by
- * {@link #obtainAttribute()}, returning them into the pool for recycling.
- */
- private void resetAttributes() {
- for (int i = 0; i < mAttributeCount; i++) {
- mAttributes[i].reset();
- }
- mAttributeCount = 0;
- }
-
- @Override
- public int getAttributeIndex(String namespace, String name) {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- for (int i = 0; i < mAttributeCount; i++) {
- if (Objects.equals(mAttributes[i].name, name)) {
- return i;
- }
- }
- return -1;
- }
-
- @Override
- public String getAttributeValue(String namespace, String name) {
- final int index = getAttributeIndex(namespace, name);
- if (index != -1) {
- return mAttributes[index].getValueString();
- } else {
- return null;
- }
- }
-
- @Override
- public String getAttributeValue(int index) {
- return mAttributes[index].getValueString();
- }
-
- @Override
- public byte[] getAttributeBytesHex(int index) throws XmlPullParserException {
- return mAttributes[index].getValueBytesHex();
- }
-
- @Override
- public byte[] getAttributeBytesBase64(int index) throws XmlPullParserException {
- return mAttributes[index].getValueBytesBase64();
- }
-
- @Override
- public int getAttributeInt(int index) throws XmlPullParserException {
- return mAttributes[index].getValueInt();
- }
-
- @Override
- public int getAttributeIntHex(int index) throws XmlPullParserException {
- return mAttributes[index].getValueIntHex();
- }
-
- @Override
- public long getAttributeLong(int index) throws XmlPullParserException {
- return mAttributes[index].getValueLong();
- }
-
- @Override
- public long getAttributeLongHex(int index) throws XmlPullParserException {
- return mAttributes[index].getValueLongHex();
- }
-
- @Override
- public float getAttributeFloat(int index) throws XmlPullParserException {
- return mAttributes[index].getValueFloat();
- }
-
- @Override
- public double getAttributeDouble(int index) throws XmlPullParserException {
- return mAttributes[index].getValueDouble();
- }
-
- @Override
- public boolean getAttributeBoolean(int index) throws XmlPullParserException {
- return mAttributes[index].getValueBoolean();
- }
-
- @Override
- public String getText() {
- return mCurrentText;
- }
-
- @Override
- public char[] getTextCharacters(int[] holderForStartAndLength) {
- final char[] chars = mCurrentText.toCharArray();
- holderForStartAndLength[0] = 0;
- holderForStartAndLength[1] = chars.length;
- return chars;
- }
-
- @Override
- public String getInputEncoding() {
- return StandardCharsets.UTF_8.name();
- }
-
- @Override
- public int getDepth() {
- return mCurrentDepth;
- }
-
- @Override
- public String getPositionDescription() {
- // Not very helpful, but it's the best information we have
- return "Token " + mCurrentToken + " at depth " + mCurrentDepth;
- }
-
- @Override
- public int getLineNumber() {
- return -1;
- }
-
- @Override
- public int getColumnNumber() {
- return -1;
- }
-
- @Override
- public boolean isWhitespace() throws XmlPullParserException {
- switch (mCurrentToken) {
- case IGNORABLE_WHITESPACE:
- return true;
- case TEXT:
- case CDSECT:
- return !TextUtils.isGraphic(mCurrentText);
- default:
- throw new XmlPullParserException("Not applicable for token " + mCurrentToken);
- }
- }
-
- @Override
- public String getNamespace() {
- switch (mCurrentToken) {
- case START_TAG:
- case END_TAG:
- // Namespaces are unsupported
- return NO_NAMESPACE;
- default:
- return null;
- }
- }
-
- @Override
- public String getName() {
- return mCurrentName;
- }
-
- @Override
- public String getPrefix() {
- // Prefixes are not supported
- return null;
- }
-
- @Override
- public boolean isEmptyElementTag() throws XmlPullParserException {
- switch (mCurrentToken) {
- case START_TAG:
- try {
- return (peekNextExternalToken() == END_TAG);
- } catch (IOException e) {
- throw new XmlPullParserException(e.toString());
- }
- default:
- throw new XmlPullParserException("Not at START_TAG");
- }
- }
-
- @Override
- public int getAttributeCount() {
- return mAttributeCount;
- }
-
- @Override
- public String getAttributeNamespace(int index) {
- // Namespaces are unsupported
- return NO_NAMESPACE;
- }
-
- @Override
- public String getAttributeName(int index) {
- return mAttributes[index].name;
- }
-
- @Override
- public String getAttributePrefix(int index) {
- // Prefixes are not supported
- return null;
- }
-
- @Override
- public String getAttributeType(int index) {
- // Validation is not supported
- return "CDATA";
- }
-
- @Override
- public boolean isAttributeDefault(int index) {
- // Validation is not supported
- return false;
- }
-
- @Override
- public int getEventType() throws XmlPullParserException {
- return mCurrentToken;
- }
-
- @Override
- public int getNamespaceCount(int depth) throws XmlPullParserException {
- // Namespaces are unsupported
- return 0;
- }
-
- @Override
- public String getNamespacePrefix(int pos) throws XmlPullParserException {
- // Namespaces are unsupported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getNamespaceUri(int pos) throws XmlPullParserException {
- // Namespaces are unsupported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getNamespace(String prefix) {
- // Namespaces are unsupported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void defineEntityReplacementText(String entityName, String replacementText)
- throws XmlPullParserException {
- // Custom entities are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setFeature(String name, boolean state) throws XmlPullParserException {
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getFeature(String name) {
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setProperty(String name, Object value) throws XmlPullParserException {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object getProperty(String name) {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- private static IllegalArgumentException illegalNamespace() {
- throw new IllegalArgumentException("Namespaces are not supported");
- }
-
- /**
- * Holder representing a single attribute. This design enables object
- * recycling without resorting to autoboxing.
- * <p>
- * To support conversion between human-readable XML and binary XML, the
- * various accessor methods will transparently convert from/to
- * human-readable values when needed.
- */
- private static class Attribute {
- public String name;
- public int type;
-
- public String valueString;
- public byte[] valueBytes;
- public int valueInt;
- public long valueLong;
- public float valueFloat;
- public double valueDouble;
-
- public void reset() {
- name = null;
- valueString = null;
- valueBytes = null;
- }
-
- public @Nullable String getValueString() {
- switch (type) {
- case TYPE_NULL:
- return null;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- return valueString;
- case TYPE_BYTES_HEX:
- return bytesToHexString(valueBytes);
- case TYPE_BYTES_BASE64:
- return Base64.encodeToString(valueBytes, Base64.NO_WRAP);
- case TYPE_INT:
- return Integer.toString(valueInt);
- case TYPE_INT_HEX:
- return Integer.toString(valueInt, 16);
- case TYPE_LONG:
- return Long.toString(valueLong);
- case TYPE_LONG_HEX:
- return Long.toString(valueLong, 16);
- case TYPE_FLOAT:
- return Float.toString(valueFloat);
- case TYPE_DOUBLE:
- return Double.toString(valueDouble);
- case TYPE_BOOLEAN_TRUE:
- return "true";
- case TYPE_BOOLEAN_FALSE:
- return "false";
- default:
- // Unknown data type; null is the best we can offer
- return null;
- }
- }
-
- public @Nullable byte[] getValueBytesHex() throws XmlPullParserException {
- switch (type) {
- case TYPE_NULL:
- return null;
- case TYPE_BYTES_HEX:
- case TYPE_BYTES_BASE64:
- return valueBytes;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return hexStringToBytes(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public @Nullable byte[] getValueBytesBase64() throws XmlPullParserException {
- switch (type) {
- case TYPE_NULL:
- return null;
- case TYPE_BYTES_HEX:
- case TYPE_BYTES_BASE64:
- return valueBytes;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Base64.decode(valueString, Base64.NO_WRAP);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public int getValueInt() throws XmlPullParserException {
- switch (type) {
- case TYPE_INT:
- case TYPE_INT_HEX:
- return valueInt;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Integer.parseInt(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public int getValueIntHex() throws XmlPullParserException {
- switch (type) {
- case TYPE_INT:
- case TYPE_INT_HEX:
- return valueInt;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Integer.parseInt(valueString, 16);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public long getValueLong() throws XmlPullParserException {
- switch (type) {
- case TYPE_LONG:
- case TYPE_LONG_HEX:
- return valueLong;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Long.parseLong(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public long getValueLongHex() throws XmlPullParserException {
- switch (type) {
- case TYPE_LONG:
- case TYPE_LONG_HEX:
- return valueLong;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Long.parseLong(valueString, 16);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public float getValueFloat() throws XmlPullParserException {
- switch (type) {
- case TYPE_FLOAT:
- return valueFloat;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Float.parseFloat(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public double getValueDouble() throws XmlPullParserException {
- switch (type) {
- case TYPE_DOUBLE:
- return valueDouble;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- try {
- return Double.parseDouble(valueString);
- } catch (Exception e) {
- throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
-
- public boolean getValueBoolean() throws XmlPullParserException {
- switch (type) {
- case TYPE_BOOLEAN_TRUE:
- return true;
- case TYPE_BOOLEAN_FALSE:
- return false;
- case TYPE_STRING:
- case TYPE_STRING_INTERNED:
- if ("true".equalsIgnoreCase(valueString)) {
- return true;
- } else if ("false".equalsIgnoreCase(valueString)) {
- return false;
- } else {
- throw new XmlPullParserException(
- "Invalid attribute " + name + ": " + valueString);
- }
- default:
- throw new XmlPullParserException("Invalid conversion from " + type);
- }
- }
- }
-
- // NOTE: To support unbundled clients, we include an inlined copy
- // of hex conversion logic from HexDump below
- private final static char[] HEX_DIGITS =
- { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
-
- private static int toByte(char c) {
- if (c >= '0' && c <= '9') return (c - '0');
- if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
- if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
- throw new IllegalArgumentException("Invalid hex char '" + c + "'");
- }
-
- static String bytesToHexString(byte[] value) {
- final int length = value.length;
- final char[] buf = new char[length * 2];
- int bufIndex = 0;
- for (int i = 0; i < length; i++) {
- byte b = value[i];
- buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
- buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
- }
- return new String(buf);
- }
-
- static byte[] hexStringToBytes(String value) {
- final int length = value.length();
- if (length % 2 != 0) {
- throw new IllegalArgumentException("Invalid hex length " + length);
- }
- byte[] buffer = new byte[length / 2];
- for (int i = 0; i < length; i += 2) {
- buffer[i / 2] = (byte) ((toByte(value.charAt(i)) << 4)
- | toByte(value.charAt(i + 1)));
- }
- return buffer;
- }
-}
diff --git a/core/java/com/android/internal/util/BinaryXmlSerializer.java b/core/java/com/android/internal/util/BinaryXmlSerializer.java
deleted file mode 100644
index ca8e46c6c561..000000000000
--- a/core/java/com/android/internal/util/BinaryXmlSerializer.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import static org.xmlpull.v1.XmlPullParser.CDSECT;
-import static org.xmlpull.v1.XmlPullParser.COMMENT;
-import static org.xmlpull.v1.XmlPullParser.DOCDECL;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.ENTITY_REF;
-import static org.xmlpull.v1.XmlPullParser.IGNORABLE_WHITESPACE;
-import static org.xmlpull.v1.XmlPullParser.PROCESSING_INSTRUCTION;
-import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-import static org.xmlpull.v1.XmlPullParser.TEXT;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.modules.utils.TypedXmlSerializer;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-
-/**
- * Serializer that writes XML documents using a custom binary wire protocol
- * which benchmarking has shown to be 4.3x faster and use 2.4x less disk space
- * than {@code Xml.newFastSerializer()} for a typical {@code packages.xml}.
- * <p>
- * The high-level design of the wire protocol is to directly serialize the event
- * stream, while efficiently and compactly writing strongly-typed primitives
- * delivered through the {@link TypedXmlSerializer} interface.
- * <p>
- * Each serialized event is a single byte where the lower half is a normal
- * {@link XmlPullParser} token and the upper half is an optional data type
- * signal, such as {@link #TYPE_INT}.
- * <p>
- * This serializer has some specific limitations:
- * <ul>
- * <li>Only the UTF-8 encoding is supported.
- * <li>Variable length values, such as {@code byte[]} or {@link String}, are
- * limited to 65,535 bytes in length. Note that {@link String} values are stored
- * as UTF-8 on the wire.
- * <li>Namespaces, prefixes, properties, and options are unsupported.
- * </ul>
- */
-public class BinaryXmlSerializer implements TypedXmlSerializer {
- /**
- * The wire protocol always begins with a well-known magic value of
- * {@code ABX_}, representing "Android Binary XML." The final byte is a
- * version number which may be incremented as the protocol changes.
- */
- public static final byte[] PROTOCOL_MAGIC_VERSION_0 = new byte[] { 0x41, 0x42, 0x58, 0x00 };
-
- /**
- * Internal token which represents an attribute associated with the most
- * recent {@link #START_TAG} token.
- */
- static final int ATTRIBUTE = 15;
-
- static final int TYPE_NULL = 1 << 4;
- static final int TYPE_STRING = 2 << 4;
- static final int TYPE_STRING_INTERNED = 3 << 4;
- static final int TYPE_BYTES_HEX = 4 << 4;
- static final int TYPE_BYTES_BASE64 = 5 << 4;
- static final int TYPE_INT = 6 << 4;
- static final int TYPE_INT_HEX = 7 << 4;
- static final int TYPE_LONG = 8 << 4;
- static final int TYPE_LONG_HEX = 9 << 4;
- static final int TYPE_FLOAT = 10 << 4;
- static final int TYPE_DOUBLE = 11 << 4;
- static final int TYPE_BOOLEAN_TRUE = 12 << 4;
- static final int TYPE_BOOLEAN_FALSE = 13 << 4;
-
- private FastDataOutput mOut;
-
- /**
- * Stack of tags which are currently active via {@link #startTag} and which
- * haven't been terminated via {@link #endTag}.
- */
- private int mTagCount = 0;
- private String[] mTagNames;
-
- /**
- * Write the given token and optional {@link String} into our buffer.
- */
- private void writeToken(int token, @Nullable String text) throws IOException {
- if (text != null) {
- mOut.writeByte(token | TYPE_STRING);
- mOut.writeUTF(text);
- } else {
- mOut.writeByte(token | TYPE_NULL);
- }
- }
-
- @Override
- public void setOutput(@NonNull OutputStream os, @Nullable String encoding) throws IOException {
- if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
- throw new UnsupportedOperationException();
- }
-
- mOut = obtainFastDataOutput(os);
- mOut.write(PROTOCOL_MAGIC_VERSION_0);
-
- mTagCount = 0;
- mTagNames = new String[8];
- }
-
- @NonNull
- protected FastDataOutput obtainFastDataOutput(@NonNull OutputStream os) {
- return FastDataOutput.obtain(os);
- }
-
- @Override
- public void setOutput(Writer writer) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void flush() throws IOException {
- if (mOut != null) {
- mOut.flush();
- }
- }
-
- @Override
- public void startDocument(@Nullable String encoding, @Nullable Boolean standalone)
- throws IOException {
- if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
- throw new UnsupportedOperationException();
- }
- if (standalone != null && !standalone) {
- throw new UnsupportedOperationException();
- }
- mOut.writeByte(START_DOCUMENT | TYPE_NULL);
- }
-
- @Override
- public void endDocument() throws IOException {
- mOut.writeByte(END_DOCUMENT | TYPE_NULL);
- flush();
-
- mOut.release();
- mOut = null;
- }
-
- @Override
- public int getDepth() {
- return mTagCount;
- }
-
- @Override
- public String getNamespace() {
- // Namespaces are unsupported
- return XmlPullParser.NO_NAMESPACE;
- }
-
- @Override
- public String getName() {
- return mTagNames[mTagCount - 1];
- }
-
- @Override
- public XmlSerializer startTag(String namespace, String name) throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- if (mTagCount == mTagNames.length) {
- mTagNames = Arrays.copyOf(mTagNames, mTagCount + (mTagCount >> 1));
- }
- mTagNames[mTagCount++] = name;
- mOut.writeByte(START_TAG | TYPE_STRING_INTERNED);
- mOut.writeInternedUTF(name);
- return this;
- }
-
- @Override
- public XmlSerializer endTag(String namespace, String name) throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mTagCount--;
- mOut.writeByte(END_TAG | TYPE_STRING_INTERNED);
- mOut.writeInternedUTF(name);
- return this;
- }
-
- @Override
- public XmlSerializer attribute(String namespace, String name, String value) throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_STRING);
- mOut.writeInternedUTF(name);
- mOut.writeUTF(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeInterned(String namespace, String name, String value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_STRING_INTERNED);
- mOut.writeInternedUTF(name);
- mOut.writeInternedUTF(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeBytesHex(String namespace, String name, byte[] value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_BYTES_HEX);
- mOut.writeInternedUTF(name);
- mOut.writeShort(value.length);
- mOut.write(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
- mOut.writeInternedUTF(name);
- mOut.writeShort(value.length);
- mOut.write(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeInt(String namespace, String name, int value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_INT);
- mOut.writeInternedUTF(name);
- mOut.writeInt(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeIntHex(String namespace, String name, int value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_INT_HEX);
- mOut.writeInternedUTF(name);
- mOut.writeInt(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeLong(String namespace, String name, long value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_LONG);
- mOut.writeInternedUTF(name);
- mOut.writeLong(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeLongHex(String namespace, String name, long value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_LONG_HEX);
- mOut.writeInternedUTF(name);
- mOut.writeLong(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeFloat(String namespace, String name, float value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_FLOAT);
- mOut.writeInternedUTF(name);
- mOut.writeFloat(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeDouble(String namespace, String name, double value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- mOut.writeByte(ATTRIBUTE | TYPE_DOUBLE);
- mOut.writeInternedUTF(name);
- mOut.writeDouble(value);
- return this;
- }
-
- @Override
- public XmlSerializer attributeBoolean(String namespace, String name, boolean value)
- throws IOException {
- if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
- if (value) {
- mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_TRUE);
- mOut.writeInternedUTF(name);
- } else {
- mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_FALSE);
- mOut.writeInternedUTF(name);
- }
- return this;
- }
-
- @Override
- public XmlSerializer text(char[] buf, int start, int len) throws IOException {
- writeToken(TEXT, new String(buf, start, len));
- return this;
- }
-
- @Override
- public XmlSerializer text(String text) throws IOException {
- writeToken(TEXT, text);
- return this;
- }
-
- @Override
- public void cdsect(String text) throws IOException {
- writeToken(CDSECT, text);
- }
-
- @Override
- public void entityRef(String text) throws IOException {
- writeToken(ENTITY_REF, text);
- }
-
- @Override
- public void processingInstruction(String text) throws IOException {
- writeToken(PROCESSING_INSTRUCTION, text);
- }
-
- @Override
- public void comment(String text) throws IOException {
- writeToken(COMMENT, text);
- }
-
- @Override
- public void docdecl(String text) throws IOException {
- writeToken(DOCDECL, text);
- }
-
- @Override
- public void ignorableWhitespace(String text) throws IOException {
- writeToken(IGNORABLE_WHITESPACE, text);
- }
-
- @Override
- public void setFeature(String name, boolean state) {
- // Quietly handle no-op features
- if ("http://xmlpull.org/v1/doc/features.html#indent-output".equals(name)) {
- return;
- }
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean getFeature(String name) {
- // Features are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setProperty(String name, Object value) {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object getProperty(String name) {
- // Properties are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setPrefix(String prefix, String namespace) {
- // Prefixes are not supported
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getPrefix(String namespace, boolean generatePrefix) {
- // Prefixes are not supported
- throw new UnsupportedOperationException();
- }
-
- private static IllegalArgumentException illegalNamespace() {
- throw new IllegalArgumentException("Namespaces are not supported");
- }
-}
diff --git a/core/java/com/android/internal/util/FastDataInput.java b/core/java/com/android/internal/util/FastDataInput.java
deleted file mode 100644
index ed6697deb6af..000000000000
--- a/core/java/com/android/internal/util/FastDataInput.java
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import android.annotation.NonNull;
-
-import dalvik.system.VMRuntime;
-
-import java.io.BufferedInputStream;
-import java.io.Closeable;
-import java.io.DataInput;
-import java.io.DataInputStream;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Optimized implementation of {@link DataInput} which buffers data in memory
- * from the underlying {@link InputStream}.
- * <p>
- * Benchmarks have demonstrated this class is 3x more efficient than using a
- * {@link DataInputStream} with a {@link BufferedInputStream}.
- */
-public class FastDataInput implements DataInput, Closeable {
- protected static final int MAX_UNSIGNED_SHORT = 65_535;
-
- protected static final int DEFAULT_BUFFER_SIZE = 32_768;
-
- protected final VMRuntime mRuntime;
-
- protected final byte[] mBuffer;
- protected final int mBufferCap;
-
- private InputStream mIn;
- protected int mBufferPos;
- protected int mBufferLim;
-
- /**
- * Values that have been "interned" by {@link #readInternedUTF()}.
- */
- private int mStringRefCount = 0;
- private String[] mStringRefs = new String[32];
-
- public FastDataInput(@NonNull InputStream in, int bufferSize) {
- mRuntime = VMRuntime.getRuntime();
- mIn = Objects.requireNonNull(in);
- if (bufferSize < 8) {
- throw new IllegalArgumentException();
- }
-
- mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
- mBufferCap = mBuffer.length;
- }
-
- /**
- * Obtain a {@link FastDataInput} configured with the given
- * {@link InputStream} and which encodes large code-points using 3-byte
- * sequences.
- * <p>
- * This <em>is</em> compatible with the {@link DataInput} API contract,
- * which specifies that large code-points must be encoded with 3-byte
- * sequences.
- */
- public static FastDataInput obtain(@NonNull InputStream in) {
- return new FastDataInput(in, DEFAULT_BUFFER_SIZE);
- }
-
- /**
- * Release a {@link FastDataInput} to potentially be recycled. You must not
- * interact with the object after releasing it.
- */
- public void release() {
- mIn = null;
- mBufferPos = 0;
- mBufferLim = 0;
- mStringRefCount = 0;
- }
-
- /**
- * Re-initializes the object for the new input.
- */
- protected void setInput(@NonNull InputStream in) {
- if (mIn != null) {
- throw new IllegalStateException("setInput() called before calling release()");
- }
-
- mIn = Objects.requireNonNull(in);
- mBufferPos = 0;
- mBufferLim = 0;
- mStringRefCount = 0;
- }
-
- protected void fill(int need) throws IOException {
- final int remain = mBufferLim - mBufferPos;
- System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain);
- mBufferPos = 0;
- mBufferLim = remain;
- need -= remain;
-
- while (need > 0) {
- int c = mIn.read(mBuffer, mBufferLim, mBufferCap - mBufferLim);
- if (c == -1) {
- throw new EOFException();
- } else {
- mBufferLim += c;
- need -= c;
- }
- }
- }
-
- @Override
- public void close() throws IOException {
- mIn.close();
- release();
- }
-
- @Override
- public void readFully(byte[] b) throws IOException {
- readFully(b, 0, b.length);
- }
-
- @Override
- public void readFully(byte[] b, int off, int len) throws IOException {
- // Attempt to read directly from buffer space if there's enough room,
- // otherwise fall back to chunking into place
- if (mBufferCap >= len) {
- if (mBufferLim - mBufferPos < len) fill(len);
- System.arraycopy(mBuffer, mBufferPos, b, off, len);
- mBufferPos += len;
- } else {
- final int remain = mBufferLim - mBufferPos;
- System.arraycopy(mBuffer, mBufferPos, b, off, remain);
- mBufferPos += remain;
- off += remain;
- len -= remain;
-
- while (len > 0) {
- int c = mIn.read(b, off, len);
- if (c == -1) {
- throw new EOFException();
- } else {
- off += c;
- len -= c;
- }
- }
- }
- }
-
- @Override
- public String readUTF() throws IOException {
- // Attempt to read directly from buffer space if there's enough room,
- // otherwise fall back to chunking into place
- final int len = readUnsignedShort();
- if (mBufferCap > len) {
- if (mBufferLim - mBufferPos < len) fill(len);
- final String res = ModifiedUtf8.decode(mBuffer, new char[len], mBufferPos, len);
- mBufferPos += len;
- return res;
- } else {
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
- readFully(tmp, 0, len);
- return ModifiedUtf8.decode(tmp, new char[len], 0, len);
- }
- }
-
- /**
- * Read a {@link String} value with the additional signal that the given
- * value is a candidate for being canonicalized, similar to
- * {@link String#intern()}.
- * <p>
- * Canonicalization is implemented by writing each unique string value once
- * the first time it appears, and then writing a lightweight {@code short}
- * reference when that string is written again in the future.
- *
- * @see FastDataOutput#writeInternedUTF(String)
- */
- public @NonNull String readInternedUTF() throws IOException {
- final int ref = readUnsignedShort();
- if (ref == MAX_UNSIGNED_SHORT) {
- final String s = readUTF();
-
- // We can only safely intern when we have remaining values; if we're
- // full we at least sent the string value above
- if (mStringRefCount < MAX_UNSIGNED_SHORT) {
- if (mStringRefCount == mStringRefs.length) {
- mStringRefs = Arrays.copyOf(mStringRefs,
- mStringRefCount + (mStringRefCount >> 1));
- }
- mStringRefs[mStringRefCount++] = s;
- }
-
- return s;
- } else {
- return mStringRefs[ref];
- }
- }
-
- @Override
- public boolean readBoolean() throws IOException {
- return readByte() != 0;
- }
-
- /**
- * Returns the same decoded value as {@link #readByte()} but without
- * actually consuming the underlying data.
- */
- public byte peekByte() throws IOException {
- if (mBufferLim - mBufferPos < 1) fill(1);
- return mBuffer[mBufferPos];
- }
-
- @Override
- public byte readByte() throws IOException {
- if (mBufferLim - mBufferPos < 1) fill(1);
- return mBuffer[mBufferPos++];
- }
-
- @Override
- public int readUnsignedByte() throws IOException {
- return Byte.toUnsignedInt(readByte());
- }
-
- @Override
- public short readShort() throws IOException {
- if (mBufferLim - mBufferPos < 2) fill(2);
- return (short) (((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0));
- }
-
- @Override
- public int readUnsignedShort() throws IOException {
- return Short.toUnsignedInt((short) readShort());
- }
-
- @Override
- public char readChar() throws IOException {
- return (char) readShort();
- }
-
- @Override
- public int readInt() throws IOException {
- if (mBufferLim - mBufferPos < 4) fill(4);
- return (((mBuffer[mBufferPos++] & 0xff) << 24) |
- ((mBuffer[mBufferPos++] & 0xff) << 16) |
- ((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0));
- }
-
- @Override
- public long readLong() throws IOException {
- if (mBufferLim - mBufferPos < 8) fill(8);
- int h = ((mBuffer[mBufferPos++] & 0xff) << 24) |
- ((mBuffer[mBufferPos++] & 0xff) << 16) |
- ((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0);
- int l = ((mBuffer[mBufferPos++] & 0xff) << 24) |
- ((mBuffer[mBufferPos++] & 0xff) << 16) |
- ((mBuffer[mBufferPos++] & 0xff) << 8) |
- ((mBuffer[mBufferPos++] & 0xff) << 0);
- return (((long) h) << 32L) | ((long) l) & 0xffffffffL;
- }
-
- @Override
- public float readFloat() throws IOException {
- return Float.intBitsToFloat(readInt());
- }
-
- @Override
- public double readDouble() throws IOException {
- return Double.longBitsToDouble(readLong());
- }
-
- @Override
- public int skipBytes(int n) throws IOException {
- // Callers should read data piecemeal
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String readLine() throws IOException {
- // Callers should read data piecemeal
- throw new UnsupportedOperationException();
- }
-}
diff --git a/core/java/com/android/internal/util/FastDataOutput.java b/core/java/com/android/internal/util/FastDataOutput.java
deleted file mode 100644
index e394c7449801..000000000000
--- a/core/java/com/android/internal/util/FastDataOutput.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.util;
-
-import android.annotation.NonNull;
-
-import dalvik.system.VMRuntime;
-
-import java.io.BufferedOutputStream;
-import java.io.Closeable;
-import java.io.DataOutput;
-import java.io.DataOutputStream;
-import java.io.Flushable;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.HashMap;
-import java.util.Objects;
-
-/**
- * Optimized implementation of {@link DataOutput} which buffers data in memory
- * before flushing to the underlying {@link OutputStream}.
- * <p>
- * Benchmarks have demonstrated this class is 2x more efficient than using a
- * {@link DataOutputStream} with a {@link BufferedOutputStream}.
- */
-public class FastDataOutput implements DataOutput, Flushable, Closeable {
- protected static final int MAX_UNSIGNED_SHORT = 65_535;
-
- protected static final int DEFAULT_BUFFER_SIZE = 32_768;
-
- protected final VMRuntime mRuntime;
-
- protected final byte[] mBuffer;
- protected final int mBufferCap;
-
- private OutputStream mOut;
- protected int mBufferPos;
-
- /**
- * Values that have been "interned" by {@link #writeInternedUTF(String)}.
- */
- private final HashMap<String, Integer> mStringRefs = new HashMap<>();
-
- public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
- mRuntime = VMRuntime.getRuntime();
- if (bufferSize < 8) {
- throw new IllegalArgumentException();
- }
-
- mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
- mBufferCap = mBuffer.length;
-
- setOutput(out);
- }
-
- /**
- * Obtain a {@link FastDataOutput} configured with the given
- * {@link OutputStream} and which encodes large code-points using 3-byte
- * sequences.
- * <p>
- * This <em>is</em> compatible with the {@link DataOutput} API contract,
- * which specifies that large code-points must be encoded with 3-byte
- * sequences.
- */
- public static FastDataOutput obtain(@NonNull OutputStream out) {
- return new FastDataOutput(out, DEFAULT_BUFFER_SIZE);
- }
-
- /**
- * Release a {@link FastDataOutput} to potentially be recycled. You must not
- * interact with the object after releasing it.
- */
- public void release() {
- if (mBufferPos > 0) {
- throw new IllegalStateException("Lingering data, call flush() before releasing.");
- }
-
- mOut = null;
- mBufferPos = 0;
- mStringRefs.clear();
- }
-
- /**
- * Re-initializes the object for the new output.
- */
- protected void setOutput(@NonNull OutputStream out) {
- if (mOut != null) {
- throw new IllegalStateException("setOutput() called before calling release()");
- }
-
- mOut = Objects.requireNonNull(out);
- mBufferPos = 0;
- mStringRefs.clear();
- }
-
- protected void drain() throws IOException {
- if (mBufferPos > 0) {
- mOut.write(mBuffer, 0, mBufferPos);
- mBufferPos = 0;
- }
- }
-
- @Override
- public void flush() throws IOException {
- drain();
- mOut.flush();
- }
-
- @Override
- public void close() throws IOException {
- mOut.close();
- release();
- }
-
- @Override
- public void write(int b) throws IOException {
- writeByte(b);
- }
-
- @Override
- public void write(byte[] b) throws IOException {
- write(b, 0, b.length);
- }
-
- @Override
- public void write(byte[] b, int off, int len) throws IOException {
- if (mBufferCap < len) {
- drain();
- mOut.write(b, off, len);
- } else {
- if (mBufferCap - mBufferPos < len) drain();
- System.arraycopy(b, off, mBuffer, mBufferPos, len);
- mBufferPos += len;
- }
- }
-
- @Override
- public void writeUTF(String s) throws IOException {
- final int len = (int) ModifiedUtf8.countBytes(s, false);
- if (len > MAX_UNSIGNED_SHORT) {
- throw new IOException("Modified UTF-8 length too large: " + len);
- }
-
- // Attempt to write directly to buffer space if there's enough room,
- // otherwise fall back to chunking into place
- if (mBufferCap >= 2 + len) {
- if (mBufferCap - mBufferPos < 2 + len) drain();
- writeShort(len);
- ModifiedUtf8.encode(mBuffer, mBufferPos, s);
- mBufferPos += len;
- } else {
- final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
- ModifiedUtf8.encode(tmp, 0, s);
- writeShort(len);
- write(tmp, 0, len);
- }
- }
-
- /**
- * Write a {@link String} value with the additional signal that the given
- * value is a candidate for being canonicalized, similar to
- * {@link String#intern()}.
- * <p>
- * Canonicalization is implemented by writing each unique string value once
- * the first time it appears, and then writing a lightweight {@code short}
- * reference when that string is written again in the future.
- *
- * @see FastDataInput#readInternedUTF()
- */
- public void writeInternedUTF(@NonNull String s) throws IOException {
- Integer ref = mStringRefs.get(s);
- if (ref != null) {
- writeShort(ref);
- } else {
- writeShort(MAX_UNSIGNED_SHORT);
- writeUTF(s);
-
- // We can only safely intern when we have remaining values; if we're
- // full we at least sent the string value above
- ref = mStringRefs.size();
- if (ref < MAX_UNSIGNED_SHORT) {
- mStringRefs.put(s, ref);
- }
- }
- }
-
- @Override
- public void writeBoolean(boolean v) throws IOException {
- writeByte(v ? 1 : 0);
- }
-
- @Override
- public void writeByte(int v) throws IOException {
- if (mBufferCap - mBufferPos < 1) drain();
- mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
- }
-
- @Override
- public void writeShort(int v) throws IOException {
- if (mBufferCap - mBufferPos < 2) drain();
- mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
- }
-
- @Override
- public void writeChar(int v) throws IOException {
- writeShort((short) v);
- }
-
- @Override
- public void writeInt(int v) throws IOException {
- if (mBufferCap - mBufferPos < 4) drain();
- mBuffer[mBufferPos++] = (byte) ((v >> 24) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 16) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
- }
-
- @Override
- public void writeLong(long v) throws IOException {
- if (mBufferCap - mBufferPos < 8) drain();
- int i = (int) (v >> 32);
- mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
- i = (int) v;
- mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
- mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
- }
-
- @Override
- public void writeFloat(float v) throws IOException {
- writeInt(Float.floatToIntBits(v));
- }
-
- @Override
- public void writeDouble(double v) throws IOException {
- writeLong(Double.doubleToLongBits(v));
- }
-
- @Override
- public void writeBytes(String s) throws IOException {
- // Callers should use writeUTF()
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void writeChars(String s) throws IOException {
- // Callers should use writeUTF()
- throw new UnsupportedOperationException();
- }
-}
diff --git a/core/java/com/android/internal/util/ModifiedUtf8.java b/core/java/com/android/internal/util/ModifiedUtf8.java
deleted file mode 100644
index a144c0034dd2..000000000000
--- a/core/java/com/android/internal/util/ModifiedUtf8.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.util;
-
-import java.io.UTFDataFormatException;
-
-public class ModifiedUtf8 {
- /**
- * Decodes a byte array containing <i>modified UTF-8</i> bytes into a string.
- *
- * <p>Note that although this method decodes the (supposedly impossible) zero byte to U+0000,
- * that's what the RI does too.
- */
- public static String decode(byte[] in, char[] out, int offset, int utfSize)
- throws UTFDataFormatException {
- int count = 0, s = 0, a;
- while (count < utfSize) {
- if ((out[s] = (char) in[offset + count++]) < '\u0080') {
- s++;
- } else if (((a = out[s]) & 0xe0) == 0xc0) {
- if (count >= utfSize) {
- throw new UTFDataFormatException("bad second byte at " + count);
- }
- int b = in[offset + count++];
- if ((b & 0xC0) != 0x80) {
- throw new UTFDataFormatException("bad second byte at " + (count - 1));
- }
- out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
- } else if ((a & 0xf0) == 0xe0) {
- if (count + 1 >= utfSize) {
- throw new UTFDataFormatException("bad third byte at " + (count + 1));
- }
- int b = in[offset + count++];
- int c = in[offset + count++];
- if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
- throw new UTFDataFormatException("bad second or third byte at " + (count - 2));
- }
- out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
- } else {
- throw new UTFDataFormatException("bad byte at " + (count - 1));
- }
- }
- return new String(out, 0, s);
- }
-
- /**
- * Returns the number of bytes the modified UTF-8 representation of 's' would take. Note
- * that this is just the space for the bytes representing the characters, not the length
- * which precedes those bytes, because different callers represent the length differently,
- * as two, four, or even eight bytes. If {@code shortLength} is true, we'll throw an
- * exception if the string is too long for its length to be represented by a short.
- */
- public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
- long result = 0;
- final int length = s.length();
- for (int i = 0; i < length; ++i) {
- char ch = s.charAt(i);
- if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
- ++result;
- } else if (ch <= 2047) {
- result += 2;
- } else {
- result += 3;
- }
- if (shortLength && result > 65535) {
- throw new UTFDataFormatException("String more than 65535 UTF bytes long");
- }
- }
- return result;
- }
-
- /**
- * Encodes the <i>modified UTF-8</i> bytes corresponding to string {@code s} into the
- * byte array {@code dst}, starting at the given {@code offset}.
- */
- public static void encode(byte[] dst, int offset, String s) {
- final int length = s.length();
- for (int i = 0; i < length; i++) {
- char ch = s.charAt(i);
- if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
- dst[offset++] = (byte) ch;
- } else if (ch <= 2047) {
- dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
- dst[offset++] = (byte) (0x80 | (0x3f & ch));
- } else {
- dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
- dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
- dst[offset++] = (byte) (0x80 | (0x3f & ch));
- }
- }
- }
-
- private ModifiedUtf8() {
- }
-}
diff --git a/core/java/com/android/modules/utils/TypedXmlPullParser.java b/core/java/com/android/modules/utils/TypedXmlPullParser.java
deleted file mode 100644
index 8f870909c7d5..000000000000
--- a/core/java/com/android/modules/utils/TypedXmlPullParser.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.modules.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-/**
- * Specialization of {@link XmlPullParser} which adds explicit methods to
- * support consistent and efficient conversion of primitive data types.
- */
-public interface TypedXmlPullParser extends XmlPullParser {
- /**
- * @return index of requested attribute, otherwise {@code -1} if undefined
- */
- default int getAttributeIndex(@Nullable String namespace, @NonNull String name) {
- final boolean namespaceNull = (namespace == null);
- final int count = getAttributeCount();
- for (int i = 0; i < count; i++) {
- if ((namespaceNull || namespace.equals(getAttributeNamespace(i)))
- && name.equals(getAttributeName(i))) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * @return index of requested attribute
- * @throws XmlPullParserException if the value is undefined
- */
- default int getAttributeIndexOrThrow(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) {
- throw new XmlPullParserException("Missing attribute " + name);
- } else {
- return index;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- @NonNull byte[] getAttributeBytesHex(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- @NonNull byte[] getAttributeBytesBase64(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- int getAttributeInt(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- int getAttributeIntHex(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- long getAttributeLong(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- long getAttributeLongHex(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- float getAttributeFloat(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- double getAttributeDouble(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed
- */
- boolean getAttributeBoolean(int index) throws XmlPullParserException;
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default @NonNull byte[] getAttributeBytesHex(@Nullable String namespace,
- @NonNull String name) throws XmlPullParserException {
- return getAttributeBytesHex(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default @NonNull byte[] getAttributeBytesBase64(@Nullable String namespace,
- @NonNull String name) throws XmlPullParserException {
- return getAttributeBytesBase64(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default int getAttributeInt(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeInt(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default int getAttributeIntHex(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeIntHex(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default long getAttributeLong(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeLong(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default long getAttributeLongHex(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeLongHex(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default float getAttributeFloat(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeFloat(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default double getAttributeDouble(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeDouble(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}
- * @throws XmlPullParserException if the value is malformed or undefined
- */
- default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name)
- throws XmlPullParserException {
- return getAttributeBoolean(getAttributeIndexOrThrow(namespace, name));
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default @Nullable byte[] getAttributeBytesHex(@Nullable String namespace,
- @NonNull String name, @Nullable byte[] defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeBytesHex(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default @Nullable byte[] getAttributeBytesBase64(@Nullable String namespace,
- @NonNull String name, @Nullable byte[] defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeBytesBase64(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default int getAttributeInt(@Nullable String namespace, @NonNull String name,
- int defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeInt(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default int getAttributeIntHex(@Nullable String namespace, @NonNull String name,
- int defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeIntHex(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default long getAttributeLong(@Nullable String namespace, @NonNull String name,
- long defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeLong(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default long getAttributeLongHex(@Nullable String namespace, @NonNull String name,
- long defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeLongHex(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default float getAttributeFloat(@Nullable String namespace, @NonNull String name,
- float defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeFloat(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default double getAttributeDouble(@Nullable String namespace, @NonNull String name,
- double defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeDouble(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-
- /**
- * @return decoded strongly-typed {@link #getAttributeValue}, otherwise
- * default value if the value is malformed or undefined
- */
- default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name,
- boolean defaultValue) {
- final int index = getAttributeIndex(namespace, name);
- if (index == -1) return defaultValue;
- try {
- return getAttributeBoolean(index);
- } catch (Exception ignored) {
- return defaultValue;
- }
- }
-}
diff --git a/core/java/com/android/modules/utils/TypedXmlSerializer.java b/core/java/com/android/modules/utils/TypedXmlSerializer.java
deleted file mode 100644
index 872e1e4ed9bb..000000000000
--- a/core/java/com/android/modules/utils/TypedXmlSerializer.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.modules.utils;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-
-/**
- * Specialization of {@link XmlSerializer} which adds explicit methods to
- * support consistent and efficient conversion of primitive data types.
- */
-public interface TypedXmlSerializer extends XmlSerializer {
- /**
- * Functionally equivalent to {@link #attribute(String, String, String)} but
- * with the additional signal that the given value is a candidate for being
- * canonicalized, similar to {@link String#intern()}.
- */
- @NonNull XmlSerializer attributeInterned(@Nullable String namespace, @NonNull String name,
- @NonNull String value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeBytesHex(@Nullable String namespace, @NonNull String name,
- @NonNull byte[] value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeBytesBase64(@Nullable String namespace, @NonNull String name,
- @NonNull byte[] value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeInt(@Nullable String namespace, @NonNull String name,
- int value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeIntHex(@Nullable String namespace, @NonNull String name,
- int value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeLong(@Nullable String namespace, @NonNull String name,
- long value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeLongHex(@Nullable String namespace, @NonNull String name,
- long value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeFloat(@Nullable String namespace, @NonNull String name,
- float value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeDouble(@Nullable String namespace, @NonNull String name,
- double value) throws IOException;
-
- /**
- * Encode the given strongly-typed value and serialize using
- * {@link #attribute(String, String, String)}.
- */
- @NonNull XmlSerializer attributeBoolean(@Nullable String namespace, @NonNull String name,
- boolean value) throws IOException;
-}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d59a51ab9174..c50abb3ff42a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -329,7 +329,6 @@ cc_library_shared {
header_libs: [
"bionic_libc_platform_headers",
"dnsproxyd_protocol_headers",
- "libandroid_runtime_vm_headers",
],
},
host: {
@@ -418,24 +417,3 @@ cc_library_shared {
never: true,
},
}
-
-cc_library_headers {
- name: "libandroid_runtime_vm_headers",
- host_supported: true,
- vendor_available: true,
- // TODO(b/153609531): remove when libbinder is not native_bridge_supported
- native_bridge_supported: true,
- // Allow only modules from the following list to create threads that can be
- // attached to the JVM. This list should be a subset of the dependencies of
- // libandroid_runtime.
- visibility: [
- "//frameworks/native/libs/binder",
- ],
- export_include_dirs: ["include_vm"],
- header_libs: [
- "jni_headers",
- ],
- export_header_lib_headers: [
- "jni_headers",
- ],
-}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 422bdc98e9b5..9da28a3e56d2 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -22,7 +22,6 @@
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
#include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/vm.h>
#include <assert.h>
#include <binder/IBinder.h>
#include <binder/IPCThreadState.h>
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 1f64df49cb56..4d8dac1daaf0 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -116,6 +116,11 @@ static void android_os_Parcel_markForBinder(JNIEnv* env, jclass clazz, jlong nat
}
}
+static jboolean android_os_Parcel_isForRpc(jlong nativePtr) {
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ return parcel ? parcel->isForRpc() : false;
+}
+
static jint android_os_Parcel_dataSize(jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
@@ -808,6 +813,8 @@ static const JNINativeMethod gParcelMethods[] = {
// @FastNative
{"nativeMarkForBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_markForBinder},
// @CriticalNative
+ {"nativeIsForRpc", "(J)Z", (void*)android_os_Parcel_isForRpc},
+ // @CriticalNative
{"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize},
// @CriticalNative
{"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail},
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 39ec0374dc5e..b2994f41af4b 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -70,6 +70,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(),
deviceInfo.hasSensor(), deviceInfo.hasBattery(),
deviceInfo.supportsUsi()));
+ // Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking
+ // it to apps that do not have the Bluetooth permission.
const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (const InputDeviceInfo::MotionRange& range: ranges) {
diff --git a/core/jni/android_view_VelocityTracker.cpp b/core/jni/android_view_VelocityTracker.cpp
index 16b9f008ec65..343e9d8b41ca 100644
--- a/core/jni/android_view_VelocityTracker.cpp
+++ b/core/jni/android_view_VelocityTracker.cpp
@@ -153,6 +153,11 @@ static jboolean android_view_VelocityTracker_nativeGetEstimator(JNIEnv* env, jcl
return result;
}
+static jboolean android_view_VelocityTracker_nativeIsAxisSupported(JNIEnv* env, jclass clazz,
+ jint axis) {
+ return VelocityTracker::isAxisSupported(axis);
+}
+
// --- JNI Registration ---
static const JNINativeMethod gVelocityTrackerMethods[] = {
@@ -167,6 +172,8 @@ static const JNINativeMethod gVelocityTrackerMethods[] = {
{"nativeGetVelocity", "(JII)F", (void*)android_view_VelocityTracker_nativeGetVelocity},
{"nativeGetEstimator", "(JIILandroid/view/VelocityTracker$Estimator;)Z",
(void*)android_view_VelocityTracker_nativeGetEstimator},
+ {"nativeIsAxisSupported", "(I)Z",
+ (void*)android_view_VelocityTracker_nativeIsAxisSupported},
};
int register_android_view_VelocityTracker(JNIEnv* env) {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 5099dd20a6d5..9e4f63cab86c 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -977,6 +977,7 @@ message UserControllerProto {
optional int32 profile = 2;
}
repeated UserProfile user_profile_group_ids = 4;
+ repeated int32 visible_users_array = 5;
}
// sync with com.android.server.am.AppTimeTracker.java
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 554b15374943..62c584847c0f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6622,6 +6622,15 @@
<permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
android:protectionLevel="internal|role" />
+ <!-- Allows applications to use the long running jobs APIs.
+ <p>This is a special access permission that can be revoked by the system or the user.
+ <p>Apps need to target API {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above
+ to be able to request this permission.
+ <p>Protection level: appop
+ -->
+ <permission android:name="android.permission.RUN_LONG_JOBS"
+ android:protectionLevel="normal|appop"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/color/letterbox_background.xml b/core/res/res/color/letterbox_background.xml
new file mode 100644
index 000000000000..955948ad2b6a
--- /dev/null
+++ b/core/res/res/color/letterbox_background.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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/system_neutral1_500" android:lStar="5" />
+</selector>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 4b9abad0f5a7..0ebce4019258 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Snelsluit"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nuwe kennisgewing"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuele sleutelbord"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fisieke sleutelbord"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sekuriteit"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Motormodus"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index b6fdcdf463c9..a861f3cffa6b 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"መቆለፊያ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"አዲስ ማሳወቂያ"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ምናባዊ የቁልፍ ሰሌዳ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"አካላዊ ቁልፍ ሰሌዳ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ደህንነት"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"የመኪና ሁነታ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 977cf8e865da..7623fb1682da 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -268,7 +268,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"إلغاء التأمين"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"إشعار جديد"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"لوحة المفاتيح الافتراضية"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"لوحة المفاتيح الخارجية"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"الأمان"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"وضع السيارة"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index dbce5958b25a..3a97bafe26cf 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"লকডাউন"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"৯৯৯+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"নতুন জাননী"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ভাৰ্শ্বুৱল কীব\'ৰ্ড"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"কায়িক কীব’ৰ্ড"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"সুৰক্ষা"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"গাড়ী ম\'ড"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 586adeff170f..0b361aca0d96 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Kilidləyin"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Yeni bildiriş"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual klaviatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziki klaviatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Güvənlik"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Avtomobil rejimi"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index c19338d5bd8b..078c098efd58 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Novo obaveštenje"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelna tastatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Bezbednost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Režim rada u automobilu"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index ab338c7fe9ca..023e82c774ec 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Блакіроўка"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Новае апавяшчэнне"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Віртуальная клавіятура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізічная клавіятура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Бяспека"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Рэжым \"У машыне\""</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 46ab1ad8a507..aaa080a355ba 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Заключване"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ново известие"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуална клавиатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическа клавиатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Сигурност"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Моторежим"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ed77eef077eb..ee1db8e89ebd 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"লকডাউন"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"৯৯৯+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"নতুন বিজ্ঞপ্তি"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ভার্চুয়াল কীবোর্ড"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ফিজিক্যাল কীবোর্ড"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"নিরাপত্তা"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"গাড়ি মোড"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 081d8f2588ac..20f6bc12a952 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Novo obavještenje"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelna tastatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sigurnost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Način rada u automobilu"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index aad566837095..2142b6093dbb 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueig de seguretat"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"+999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificació nova"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclat virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclat físic"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguretat"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode de cotxe"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index e9d5ab276ea8..7720d083409c 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zamknuto"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nové oznámení"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuální klávesnice"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyzická klávesnice"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Zabezpečení"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Režim Auto"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index edb962c5c078..ecd64070a75b 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lås enhed"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ny notifikation"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelt tastatur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysisk tastatur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sikkerhed"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Biltilstand"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index b7a0b0281517..3d5985c9f1cc 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Sperren"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Neue Benachrichtigung"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Bildschirmtastatur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physische Tastatur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sicherheit"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automodus"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index a9dd1cf2b8fb..f84a9fb01066 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Κλείδωμα"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Νέα ειδοποίηση"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Εικονικό πληκτρολόγιο"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Κανονικό πληκτρολόγιο"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Ασφάλεια"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Λειτουργία αυτοκινήτου"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 1cc8d5014891..4c0510bef4cf 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 6fa02f3b5a6c..875ddf9f3829 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index fac706e3f27f..6e034b730083 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 55c121ac4a01..643f27f49220 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Security"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 1b190e3e9b40..91e99fff4c7a 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‎‎‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎Lockdown‎‏‎‎‏‎"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‎999+‎‏‎‎‏‎"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎New notification‎‏‎‎‏‎"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎Virtual keyboard‎‏‎‎‏‎"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎Physical keyboard‎‏‎‎‏‎"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‏‎Security‎‏‎‎‏‎"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‏‎‏‎‎‎Car mode‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 106935c24603..6a45205a3558 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo auto"</string>
@@ -1970,8 +1969,8 @@
<string name="usb_mtp_launch_notification_description" msgid="6942535713629852684">"Presiona para ver archivos"</string>
<string name="pin_target" msgid="8036028973110156895">"Fijar"</string>
<string name="pin_specific_target" msgid="7824671240625957415">"Fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
- <string name="unpin_target" msgid="3963318576590204447">"No fijar"</string>
- <string name="unpin_specific_target" msgid="3859828252160908146">"No fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
+ <string name="unpin_target" msgid="3963318576590204447">"Dejar de fijar"</string>
+ <string name="unpin_specific_target" msgid="3859828252160908146">"Dejar de fijar <xliff:g id="LABEL">%1$s</xliff:g>"</string>
<string name="app_info" msgid="6113278084877079851">"Información de apps"</string>
<string name="negative_duration" msgid="1938335096972945232">"−<xliff:g id="TIME">%1$s</xliff:g>"</string>
<string name="demo_starting_message" msgid="6577581216125805905">"Iniciando demostración…"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 3ae013b04365..66f67b368c72 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo de seguridad"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt; 999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo de coche"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 182aa65cf8e0..349a6b25c0c5 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lukustamine"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Uus märguanne"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuaalne klaviatuur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Füüsiline klaviatuur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Turvalisus"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Autorežiim"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 31cc0b6cd24d..d4759d5bf77b 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blokeoa"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Jakinarazpen berria"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teklatu birtuala"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teklatu fisikoa"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurtasuna"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Auto modua"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 58a7f6293f67..4064353038b8 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"قفل همه"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"۹۹۹+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"اعلان جدید"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"صفحه‌‌کلید مجازی"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"صفحه‌کلید فیزیکی"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"امنیت"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"حالت خودرو"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 31d2571c79aa..8fedfb74fd1b 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lukitse"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Uusi ilmoitus"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuaalinen näppäimistö"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyysinen näppäimistö"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Turvallisuus"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Autotila"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 5150da961437..e63b73436df6 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Verrouillage"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nouvelle notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Clavier virtuel"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Clavier physique"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sécurité"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode Voiture"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 3736890de06a..019fdf2840b5 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Verrouiller"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nouvelle notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Clavier virtuel"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Clavier physique"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sécurité"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode Voiture"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 7575d6865179..219299fed39a 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloquear"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nova"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguranza"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo coche"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 42bad0a5107c..90dda1aa6841 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"લૉકડાઉન"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"નવું નોટિફિકેશન"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"વર્ચ્યુઅલ કીબોર્ડ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ભૌતિક કીબોર્ડ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"સુરક્ષા"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"કાર મોડ"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 83caceaaaae4..af5bc1f067f0 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"फ़ाेन लॉक करें"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"नई सूचना"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"वर्चुअल कीबोर्ड"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"सामान्य कीबोर्ड"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"कार मोड"</string>
@@ -1136,7 +1135,7 @@
<string name="copy" msgid="5472512047143665218">"कॉपी करें"</string>
<string name="failed_to_copy_to_clipboard" msgid="725919885138539875">"क्लिपबोर्ड पर कॉपी नहीं हो सका"</string>
<string name="paste" msgid="461843306215520225">"चिपकाएं"</string>
- <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे पाठ के रूप में चिपकाएं"</string>
+ <string name="paste_as_plain_text" msgid="7664800665823182587">"सादे टेक्स्ट के रूप में चिपकाएं"</string>
<string name="replace" msgid="7842675434546657444">"बदलें•"</string>
<string name="delete" msgid="1514113991712129054">"मिटाएं"</string>
<string name="copyUrl" msgid="6229645005987260230">"यूआरएल को कॉपी करें"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 6f81009ae4ab..87df29a689ee 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova obavijest"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtualna tipkovnica"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tipkovnica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sigurnost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Način rada u automobilu"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 71687d4bc2a9..3762fdeb4bcd 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zárolás"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Új értesítés"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuális billentyűzet"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizikai billentyűzet"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Biztonság"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Autós üzemmód"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index b7146f05c97b..a11e24be55b5 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Արգելափակում"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Նոր ծանուցում"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Վիրտուալ ստեղնաշար"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ֆիզիկական ստեղնաշար"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Անվտանգություն"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Մեքենայի ռեժիմ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 66fc3fb6d252..dbccee947097 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Kunci total"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notifikasi baru"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Keyboard virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Keyboard fisik"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Keamanan"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mode mobil"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 64b23406fc1e..cfefc03d59d0 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Læsing"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ný tilkynning"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Sýndarlyklaborð"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Vélbúnaðarlyklaborð"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Öryggi"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Bílastilling"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index d25bb063ca10..b05bf7978904 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blocco"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nuova notifica"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Tastiera virtuale"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fisica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sicurezza"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modalità automobile"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index a1373c31877b..8656fcee9a2f 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"נעילה"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"התראה חדשה"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"מקלדת וירטואלית"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"מקלדת פיזית"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"אבטחה"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"מצב נהיגה"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index ed55a7fa8eb8..69d0b9d2c12d 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ロックダウン"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新しい通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"仮想キーボード"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"物理キーボード"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"セキュリティ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"運転モード"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 0b06d7c5ebef..6d32f2590034 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"დაბლოკვა"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ახალი შეტყობინება"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ვირტუალური კლავიატურა"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ფიზიკური კლავიატურა"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"უსაფრთხოება"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"მანქანის რეჟიმი"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index a44d09d36f51..0d9fd3ff215e 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Құлыптау"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Жаңа хабарландыру"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуалдық пернетақта"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физикалық пернетақта"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Қауіпсіздік"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Көлік режимі"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 2fada731b553..0c82b663a033 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ការចាក់​សោ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ការជូនដំណឹងថ្មី"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ក្ដារ​ចុច​និម្មិត"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ក្ដារចុច​រូបវន្ត"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"សុវត្ថិភាព"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"មុខងារ​រថយន្ត"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index c39d9f7f065d..e27527f0186c 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ಲಾಕ್‌ಡೌನ್‌"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ಹೊಸ ಅಧಿಸೂಚನೆ"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ವರ್ಚುಯಲ್ ಕೀಬೋರ್ಡ್"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್‌"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ಭದ್ರತೆ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ಕಾರ್ ಮೋಡ್"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 55bae4d0a8b1..c953a39e8386 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -247,7 +247,7 @@
<string name="bugreport_message" msgid="5212529146119624326">"현재 기기 상태에 대한 정보를 수집하여 이메일 메시지로 전송합니다. 버그 신고를 시작하여 전송할 준비가 되려면 약간 시간이 걸립니다."</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"대화형 보고서"</string>
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"대부분의 경우 이 옵션을 사용합니다. 신고 진행 상황을 추적하고 문제에 대한 세부정보를 입력하고 스크린샷을 찍을 수 있습니다. 신고하기에 시간이 너무 오래 걸리고 사용 빈도가 낮은 일부 섹션을 생략할 수 있습니다."</string>
- <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 보고서"</string>
+ <string name="bugreport_option_full_title" msgid="7681035745950045690">"전체 신고"</string>
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"기기가 응답하지 않거나 너무 느리거나 모든 보고서 섹션이 필요한 경우 이 옵션을 사용하여 시스템 방해를 최소화합니다. 세부정보를 추가하거나 스크린샷을 추가로 찍을 수 없습니다."</string>
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{버그 신고 스크린샷을 #초 후에 찍습니다.}other{버그 신고 스크린샷을 #초 후에 찍습니다.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"버그 신고용 스크린샷 촬영 완료"</string>
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"잠금"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"새 알림"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"가상 키보드"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"물리적 키보드"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"보안"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"운전 모드"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index ad01dafc9de2..dccc4a6a0d8e 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Бекем кулпулоо"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Жаңы эскертме"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуалдык баскычтоп"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Аппараттык баскычтоп"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Коопсуздук"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Унаа режими"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 02df227f37bc..c6524de6915b 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ລັອກໄວ້"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ການແຈ້ງເຕືອນໃໝ່"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ແປ້ນພິມສະເໝືອນ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ແປ້ນພິມພາຍນອກ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ຄວາມປອດໄພ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ໂໝດຂັບລົດ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 4543ef621fb7..adf30e8145e2 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Užrakinimas"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Naujas pranešimas"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtualioji klaviatūra"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizinė klaviatūra"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sauga"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automobilio režimas"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 64c855b261e6..5631521d3c49 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloķēšana"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"Pārsniedz"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Jauns paziņojums"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuālā tastatūra"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziskā tastatūra"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Drošība"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automašīnas režīms"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 0af4cdd90cec..a45d0a7a0f97 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Заклучување"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ново известување"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуелна тастатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим на работа во автомобил"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index bc12c0722541..492cd5402fb5 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ലോക്ക്‌ഡൗൺ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"പുതിയ അറിയിപ്പ്"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"വെർച്വൽ കീബോഡ്"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ഫിസിക്കൽ കീബോഡ്"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"സുരക്ഷ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"കാർ മോഡ്"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 4e8c314dbe7b..2c8aaae25565 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Түгжих"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Шинэ мэдэгдэл"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуал гар"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Биет гар"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Аюулгүй байдал"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Машины горим"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index aa8c1e9077b8..d47fea35b675 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"लॉकडाउन"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"नवीन सूचना"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"व्हर्च्युअल कीबोर्ड"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"वास्तविक कीबोर्ड"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"कार मोड"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 9a6ee3bee0da..deb343de11b1 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Kunci semua"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Pemberitahuan baharu"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Papan kekunci maya"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Papan kekunci fizikal"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Keselamatan"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mod kereta"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 716f5d709473..5f4167284217 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"လော့ခ်ဒေါင်း"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"၉၉၉+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"အကြောင်းကြားချက်အသစ်"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ပကတိအသွင်ကီးဘုတ်"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"စက်၏ ကီးဘုတ်"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"လုံခြုံရေး"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ကားမုဒ်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 3d16ea7b5249..0c9a98396e7f 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Låsing"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nytt varsel"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuelt tastatur"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysisk tastatur"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Sikkerhet"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Bilmodus"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index bdb31bc1d01b..e95d48b06947 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"लकडाउन गर्नु…"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"९९९+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"नयाँ सूचना"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"भर्चुअल किबोर्ड"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"फिजिकल किबोर्ड"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"कार मोड"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 162d3e818563..57235106789b 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999 +"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nieuwe melding"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtueel toetsenbord"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysiek toetsenbord"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Beveiliging"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Automodus"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 440245efa0dc..c4150dc43049 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ଲକ୍ କରନ୍ତୁ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ନୂଆ ବିଜ୍ଞପ୍ତି"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ଭର୍ଚୁଆଲ୍‌ କୀ\'ବୋର୍ଡ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ଫିଜିକଲ୍ କୀ’ବୋର୍ଡ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ସୁରକ୍ଷା"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"କାର୍ ମୋଡ୍"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index a9e3531bb696..96917c03b92c 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ਲਾਕਡਾਊਨ"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"ਨਵੀਂ ਸੂਚਨਾ"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ਆਭਾਸੀ ਕੀ-ਬੋਰਡ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ਸੁਰੱਖਿਆ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"ਕਾਰ ਮੋਡ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 7b47a5c15965..be9d3227a1f3 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blokada"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nowe powiadomienie"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Klawiatura wirtualna"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Klawiatura fizyczna"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Bezpieczeństwo"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Tryb samochodowy"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 8635d769d9e6..ff352b13694e 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo carro"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 5dc59e1b6347..d343af6487b2 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloquear"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo automóvel"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 8635d769d9e6..ff352b13694e 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Teclado virtual"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modo carro"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index acd1df68f0a7..b560b075254a 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blocare strictă"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"˃999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Notificare nouă"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Tastatură virtuală"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastatură fizică"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Securitate"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Mod Mașină"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index fb4775bf661f..fbe67e2617e1 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка входа"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"&gt;999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Новое уведомление"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуальная клавиатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическая клавиатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безопасность"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим \"В авто\""</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 098e62212973..4cec8777d9c1 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"අගුලු දැමීම"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"නව දැනුම්දීම"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"අතථ්‍ය යතුරු පුවරුව"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"භෞතික යතුරු පුවරුව"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ආරක්ෂාව"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"මෝටර් රථ ආකාරය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 47e3d3bc072a..b98364a1b46a 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Uzamknúť"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Nové upozornenie"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuálna klávesnica"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyzická klávesnica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Zabezpečenie"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Režim v aute"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 6e722e3eee50..6972abb06877 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Zakleni"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999 +"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Novo obvestilo"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Navidezna tipkovnica"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizična tipkovnica"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Varnost"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Način za avtomobil"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index 0a70e9aa3d2d..cbac0f021f6e 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Blloko"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Njoftim i ri"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Tastiera virtuale"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fizike"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Siguria"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Modaliteti \"në makinë\""</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 0e50810da512..d5549e71d9bd 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -265,7 +265,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Закључавање"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ново обавештење"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Виртуелна тастатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим рада у аутомобилу"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index f8b3fdf997f5..d1c579d1eafb 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Låsning"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Ny avisering"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtuellt tangentbord"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysiskt tangentbord"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Säkerhet"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Billäge"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 5829fae5dbcf..2bc3f5765c05 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Funga"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Arifa mpya"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Kibodi pepe"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Kibodi halisi"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Usalama"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Hali ya gari"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 1533c36754ce..9e48a47acd79 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"பூட்டு"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"புதிய அறிவிப்பு"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"விர்ச்சுவல் கீபோர்டு"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"கைமுறை கீபோர்டு"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"பாதுகாப்பு"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"கார் பயன்முறை"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 313bc803837b..eff08bcbb898 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"లాక్ చేయి"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"కొత్త నోటిఫికేషన్"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"వర్చువల్ కీబోర్డ్"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"భౌతిక కీబోర్డ్"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"సెక్యూరిటీ"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"కార్‌ మోడ్"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 447b42b06b61..72cbc3661549 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"ปิดล็อก"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"การแจ้งเตือนใหม่"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"แป้นพิมพ์เสมือน"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"แป้นพิมพ์จริง"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"ความปลอดภัย"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"โหมดรถยนต์"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 9aaa1b8e6f22..e66999d6cb96 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"I-lockdown"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Bagong notification"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual na keyboard"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Pisikal na keyboard"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Car mode"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 2f80037da145..b6c4b4a61d4d 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Tam gizlilik"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Yeni bildirim"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Sanal klavye"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziksel klavye"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Güvenlik"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Araç modu"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index b7bb63b93404..c408c5133ebb 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -266,7 +266,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Блокування"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Нове сповіщення"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Віртуальна клавіатура"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізична клавіатура"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Безпека"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Режим автомобіля"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 58e342fcecda..778443158799 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"مقفل"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"‎999+‎"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"نئی اطلاع"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"ورچوئل کی بورڈ"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"فزیکل کی بورڈ"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"سیکیورٹی"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"کار وضع"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index cf4478d1c47e..5ac689d11930 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Bloklash"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Yangi bildirishnoma"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Virtual klaviatura"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tashqi klaviatura"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Xavfsizlik"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Avtomobil rejimi"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 1a1bb91f7d5d..e1b479cc295e 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Khóa"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Thông báo mới"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Bàn phím ảo"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Bàn phím vật lý"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Bảo mật"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Chế độ trên ô tô"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 4cb587bc7b90..5fce25e0ecdd 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"锁定"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"虚拟键盘"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"实体键盘"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"车载模式"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index b9185b78f093..61a3f2048fbe 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"虛擬鍵盤"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"實體鍵盤"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"車用模式"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 4baced82c846..10dc699edf9b 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"鎖定"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"超過 999"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"虛擬鍵盤"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"實體鍵盤"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"車用模式"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 23a557af6f46..66d639ebb583 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -264,7 +264,6 @@
<string name="global_action_lockdown" msgid="2475471405907902963">"Khiya"</string>
<string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string>
<string name="notification_hidden_text" msgid="2835519769868187223">"Isaziso esisha"</string>
- <string name="notification_channel_virtual_keyboard" msgid="6465975799223304567">"Ikhibhodi ebonakalayo"</string>
<string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ikhibhodi ephathekayo"</string>
<string name="notification_channel_security" msgid="8516754650348238057">"Ukuphepha"</string>
<string name="notification_channel_car_mode" msgid="2123919247040988436">"Imodi yemoto"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 173908d97b56..23dd1b407b00 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5217,7 +5217,7 @@
but isn't supported on the device or both dark scrim alpha and blur radius aren't
provided.
-->
- <color name="config_letterboxBackgroundColor">@android:color/system_neutral2_900</color>
+ <color name="config_letterboxBackgroundColor">@color/letterbox_background</color>
<!-- Horizontal position of a center of the letterboxed app window.
0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
new file mode 100644
index 000000000000..fe3ab625bacc
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -0,0 +1,433 @@
+/*
+ * 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.radio.tests.unittests;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.hardware.radio.IRadioService;
+import android.hardware.radio.ITuner;
+import android.hardware.radio.ITunerCallback;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+import android.hardware.radio.RadioMetadata;
+import android.hardware.radio.RadioTuner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class TunerAdapterTest {
+
+ private static final int CALLBACK_TIMEOUT_MS = 30_000;
+ private static final int AM_LOWER_LIMIT_KHZ = 150;
+
+ private static final RadioManager.BandConfig TEST_BAND_CONFIG = createBandConfig();
+
+ private static final ProgramSelector.Identifier FM_IDENTIFIER =
+ new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
+ /* value= */ 94300);
+ private static final ProgramSelector FM_SELECTOR =
+ new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+ private static final RadioManager.ProgramInfo FM_PROGRAM_INFO = createFmProgramInfo();
+
+ private RadioTuner mRadioTuner;
+ private ITunerCallback mTunerCallback;
+
+ @Mock
+ private IRadioService mRadioServiceMock;
+ @Mock
+ private Context mContextMock;
+ @Mock
+ private ITuner mTunerMock;
+ @Mock
+ private RadioTuner.Callback mCallbackMock;
+
+ @Before
+ public void setUp() throws Exception {
+ RadioManager radioManager = new RadioManager(mContextMock, mRadioServiceMock);
+
+ doAnswer(invocation -> {
+ mTunerCallback = (ITunerCallback) invocation.getArguments()[3];
+ return mTunerMock;
+ }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any());
+
+ doAnswer(invocation -> {
+ ProgramSelector program = (ProgramSelector) invocation.getArguments()[0];
+ if (program.getPrimaryId().getType()
+ != ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) {
+ throw new IllegalArgumentException();
+ }
+ if (program.getPrimaryId().getValue() < AM_LOWER_LIMIT_KHZ) {
+ mTunerCallback.onTuneFailed(RadioManager.STATUS_BAD_VALUE, program);
+ } else {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).tune(any());
+
+ mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, TEST_BAND_CONFIG,
+ /* withAudio= */ true, mCallbackMock, /* handler= */ null);
+ }
+
+ @After
+ public void cleanUp() throws Exception {
+ mRadioTuner.close();
+ }
+
+ @Test
+ public void close_forTunerAdapter() throws Exception {
+ mRadioTuner.close();
+
+ verify(mTunerMock).close();
+ }
+
+ @Test
+ public void setConfiguration_forTunerAdapter() throws Exception {
+ int status = mRadioTuner.setConfiguration(TEST_BAND_CONFIG);
+
+ verify(mTunerMock).setConfiguration(TEST_BAND_CONFIG);
+ assertWithMessage("Status for setting configuration")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ }
+
+ @Test
+ public void getConfiguration_forTunerAdapter() throws Exception {
+ when(mTunerMock.getConfiguration()).thenReturn(TEST_BAND_CONFIG);
+ RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
+
+ int status = mRadioTuner.getConfiguration(bandConfigs);
+
+ assertWithMessage("Status for getting configuration")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ assertWithMessage("Configuration obtained from radio tuner")
+ .that(bandConfigs[0]).isEqualTo(TEST_BAND_CONFIG);
+ }
+
+ @Test
+ public void setMute_forTunerAdapter() {
+ int status = mRadioTuner.setMute(/* mute= */ true);
+
+ assertWithMessage("Status for setting mute")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ }
+
+ @Test
+ public void getMute_forTunerAdapter() throws Exception {
+ when(mTunerMock.isMuted()).thenReturn(true);
+
+ boolean muteStatus = mRadioTuner.getMute();
+
+ assertWithMessage("Mute status").that(muteStatus).isTrue();
+ }
+
+ @Test
+ public void step_forTunerAdapter_succeeds() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+ int scanStatus = mRadioTuner.step(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+ verify(mTunerMock).step(/* skipSubChannel= */ true, /* skipSubChannel= */ false);
+ assertWithMessage("Status for stepping")
+ .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void seek_forTunerAdapter_succeeds() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+
+ int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+
+ verify(mTunerMock).scan(/* directionDown= */ true, /* skipSubChannel= */ false);
+ assertWithMessage("Status for seeking")
+ .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void seek_forTunerAdapter_invokesOnErrorWhenTimeout() throws Exception {
+ doAnswer(invocation -> {
+ mTunerCallback.onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ return RadioManager.STATUS_OK;
+ }).when(mTunerMock).scan(anyBoolean(), anyBoolean());
+
+ mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(RadioTuner.ERROR_SCAN_TIMEOUT);
+ }
+
+ @Test
+ public void tune_withChannelsForTunerAdapter_succeeds() {
+ int status = mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0);
+
+ assertWithMessage("Status for tuning with channel and sub-channel")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void tune_withValidSelectorForTunerAdapter_succeeds() throws Exception {
+ mRadioTuner.tune(FM_SELECTOR);
+
+ verify(mTunerMock).tune(FM_SELECTOR);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ }
+
+
+ @Test
+ public void tune_withInvalidSelectorForTunerAdapter_invokesOnTuneFailed() {
+ ProgramSelector invalidSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
+ new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, /* value= */ 100),
+ /* secondaryIds= */ null, /* vendorIds= */ null);
+
+ mRadioTuner.tune(invalidSelector);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onTuneFailed(RadioManager.STATUS_BAD_VALUE, invalidSelector);
+ }
+
+ @Test
+ public void cancel_forTunerAdapter() throws Exception {
+ mRadioTuner.tune(FM_SELECTOR);
+
+ mRadioTuner.cancel();
+
+ verify(mTunerMock).cancel();
+ }
+
+ @Test
+ public void cancelAnnouncement_forTunerAdapter() throws Exception {
+ mRadioTuner.cancelAnnouncement();
+
+ verify(mTunerMock).cancelAnnouncement();
+ }
+
+ @Test
+ public void getProgramInfo_beforeProgramInfoSetForTunerAdapter() {
+ RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
+
+ int status = mRadioTuner.getProgramInformation(programInfoArray);
+
+ assertWithMessage("Status for getting null program info")
+ .that(status).isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+ }
+
+ @Test
+ public void getProgramInfo_afterTuneForTunerAdapter() {
+ mRadioTuner.tune(FM_SELECTOR);
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
+ RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
+
+ int status = mRadioTuner.getProgramInformation(programInfoArray);
+
+ assertWithMessage("Status for getting program info")
+ .that(status).isEqualTo(RadioManager.STATUS_OK);
+ assertWithMessage("Program info obtained from radio tuner")
+ .that(programInfoArray[0]).isEqualTo(FM_PROGRAM_INFO);
+ }
+
+ @Test
+ public void getMetadataImage_forTunerAdapter() throws Exception {
+ Bitmap bitmapExpected = Mockito.mock(Bitmap.class);
+ when(mTunerMock.getImage(anyInt())).thenReturn(bitmapExpected);
+ int imageId = 1;
+
+ Bitmap image = mRadioTuner.getMetadataImage(/* id= */ imageId);
+
+ assertWithMessage("Image obtained from id %s", imageId)
+ .that(image).isEqualTo(bitmapExpected);
+ }
+
+ @Test
+ public void isAnalogForced_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
+
+ boolean isAnalogForced = mRadioTuner.isAnalogForced();
+
+ assertWithMessage("Forced analog playback switch")
+ .that(isAnalogForced).isTrue();
+ }
+
+ @Test
+ public void setAnalogForced_forTunerAdapter() throws Exception {
+ boolean analogForced = true;
+
+ mRadioTuner.setAnalogForced(analogForced);
+
+ verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, analogForced);
+ }
+
+ @Test
+ public void isConfigFlagSupported_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING))
+ .thenReturn(true);
+
+ boolean dabFmSoftLinking =
+ mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING);
+
+ assertWithMessage("Support for DAB-DAB linking config flag")
+ .that(dabFmSoftLinking).isTrue();
+ }
+
+ @Test
+ public void isConfigFlagSet_forTunerAdapter() throws Exception {
+ when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING))
+ .thenReturn(true);
+
+ boolean dabFmSoftLinking =
+ mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING);
+
+ assertWithMessage("DAB-FM soft linking config flag")
+ .that(dabFmSoftLinking).isTrue();
+ }
+
+ @Test
+ public void setConfigFlag_forTunerAdapter() throws Exception {
+ boolean dabFmLinking = true;
+
+ mRadioTuner.setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking);
+
+ verify(mTunerMock).setConfigFlag(RadioManager.CONFIG_DAB_FM_LINKING, dabFmLinking);
+ }
+
+ @Test
+ public void getParameters_forTunerAdapter() throws Exception {
+ List<String> parameterKeys = Arrays.asList("ParameterKeyMock");
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ when(mTunerMock.getParameters(parameterKeys)).thenReturn(parameters);
+
+ assertWithMessage("Parameters obtained from radio tuner")
+ .that(mRadioTuner.getParameters(parameterKeys)).isEqualTo(parameters);
+ }
+
+ @Test
+ public void setParameters_forTunerAdapter() throws Exception {
+ Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+ when(mTunerMock.setParameters(parameters)).thenReturn(parameters);
+
+ assertWithMessage("Parameters set for radio tuner")
+ .that(mRadioTuner.setParameters(parameters)).isEqualTo(parameters);
+ }
+
+ @Test
+ public void isAntennaConnected_forTunerAdapter() throws Exception {
+ mTunerCallback.onAntennaState(/* connected= */ false);
+
+ assertWithMessage("Antenna connection status")
+ .that(mRadioTuner.isAntennaConnected()).isFalse();
+ }
+
+ @Test
+ public void hasControl_forTunerAdapter() throws Exception {
+ when(mTunerMock.isClosed()).thenReturn(true);
+
+ assertWithMessage("Control on tuner").that(mRadioTuner.hasControl()).isFalse();
+ }
+
+ @Test
+ public void onConfigurationChanged_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onConfigurationChanged(TEST_BAND_CONFIG);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onConfigurationChanged(TEST_BAND_CONFIG);
+ }
+
+ @Test
+ public void onTrafficAnnouncement_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onTrafficAnnouncement(/* active= */ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onTrafficAnnouncement(/* active= */ true);
+ }
+
+ @Test
+ public void onEmergencyAnnouncement_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onEmergencyAnnouncement(/* active= */ true);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onEmergencyAnnouncement(/* active= */ true);
+ }
+
+ @Test
+ public void onBackgroundScanAvailabilityChange_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onBackgroundScanAvailabilityChange(/* isAvailable= */ false);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS))
+ .onBackgroundScanAvailabilityChange(/* isAvailable= */ false);
+ }
+
+ @Test
+ public void onProgramListChanged_forTunerCallbackAdapter() throws Exception {
+ mTunerCallback.onProgramListChanged();
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramListChanged();
+ }
+
+ @Test
+ public void onParametersUpdated_forTunerCallbackAdapter() throws Exception {
+ Map<String, String> parametersExpected = Map.of("ParameterKeyMock", "ParameterValueMock");
+
+ mTunerCallback.onParametersUpdated(parametersExpected);
+
+ verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onParametersUpdated(parametersExpected);
+ }
+
+ private static RadioManager.ProgramInfo createFmProgramInfo() {
+ return new RadioManager.ProgramInfo(FM_SELECTOR, FM_IDENTIFIER, FM_IDENTIFIER,
+ /* relatedContent= */ null, /* infoFlags= */ 0b110001,
+ /* signalQuality= */ 1, createRadioMetadata(), /* vendorInfo= */ null);
+ }
+
+ private static RadioManager.FmBandConfig createBandConfig() {
+ return new RadioManager.FmBandConfig(new RadioManager.FmBandDescriptor(
+ RadioManager.REGION_ITU_1, RadioManager.BAND_FM, /* lowerLimit= */ 87500,
+ /* upperLimit= */ 108000, /* spacing= */ 200, /* stereo= */ true,
+ /* rds= */ false, /* ta= */ false, /* af= */ false, /* es= */ false));
+ }
+
+ private static RadioMetadata createRadioMetadata() {
+ RadioMetadata.Builder metadataBuilder = new RadioMetadata.Builder();
+ return metadataBuilder.putString(RadioMetadata.METADATA_KEY_ARTIST, "artistMock").build();
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index e2556d67b811..2cb058bd61a3 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -15,9 +15,11 @@
*/
package com.android.server.broadcastradio.aidl;
+import android.hardware.broadcastradio.IdentifierType;
import android.hardware.broadcastradio.Metadata;
import android.hardware.broadcastradio.ProgramIdentifier;
import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
@@ -42,7 +44,7 @@ final class AidlTestUtils {
return makeProgramInfo(selector, signalQuality);
}
- static ProgramSelector makeFMSelector(long freq) {
+ static ProgramSelector makeFmSelector(long freq) {
return makeProgramSelector(ProgramSelector.PROGRAM_TYPE_FM,
new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY,
freq));
@@ -54,6 +56,18 @@ final class AidlTestUtils {
/* vendorIds= */ null);
}
+ static android.hardware.broadcastradio.ProgramSelector makeHalFmSelector(int freq) {
+ ProgramIdentifier halId = new ProgramIdentifier();
+ halId.type = IdentifierType.AMFM_FREQUENCY_KHZ;
+ halId.value = freq;
+
+ android.hardware.broadcastradio.ProgramSelector halSelector =
+ new android.hardware.broadcastradio.ProgramSelector();
+ halSelector.primaryId = halId;
+ halSelector.secondaryIds = new ProgramIdentifier[0];
+ return halSelector;
+ }
+
static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
// Note that because ConversionUtils does not by design provide functions for all
// conversions, this function only copies fields that are set by makeProgramInfo().
@@ -69,7 +83,7 @@ final class AidlTestUtils {
return hwInfo;
}
- static ProgramInfo makeHalProgramSelector(
+ static ProgramInfo makeHalProgramInfo(
android.hardware.broadcastradio.ProgramSelector hwSel, int hwSignalQuality) {
ProgramInfo hwInfo = new ProgramInfo();
hwInfo.selector = hwSel;
@@ -80,4 +94,21 @@ final class AidlTestUtils {
hwInfo.metadata = new Metadata[]{};
return hwInfo;
}
+
+ static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
+ VendorKeyValue vendorKeyValue = new VendorKeyValue();
+ vendorKeyValue.key = vendorKey;
+ vendorKeyValue.value = vendorValue;
+ return vendorKeyValue;
+ }
+
+ static android.hardware.broadcastradio.Announcement makeAnnouncement(int type,
+ int selectorFreq) {
+ android.hardware.broadcastradio.Announcement halAnnouncement =
+ new android.hardware.broadcastradio.Announcement();
+ halAnnouncement.type = (byte) type;
+ halAnnouncement.selector = makeHalFmSelector(selectorFreq);
+ halAnnouncement.vendorInfo = new VendorKeyValue[]{};
+ return halAnnouncement;
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
new file mode 100644
index 000000000000..699212a5c9f5
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AnnouncementAggregatorTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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.broadcastradio.aidl;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+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.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for AIDL HAL AnnouncementAggregator.
+ */
+@RunWith(MockitoJUnitRunner.class)
+public final class AnnouncementAggregatorTest {
+ private static final int[] TEST_ENABLED_TYPES = new int[]{Announcement.TYPE_TRAFFIC};
+
+ private final Object mLock = new Object();
+ private AnnouncementAggregator mAnnouncementAggregator;
+ private IBinder.DeathRecipient mDeathRecipient;
+
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private IBinder mBinderMock;
+ // Array of mocked radio modules
+ private RadioModule[] mRadioModuleMocks;
+ // Array of mocked close handles
+ private ICloseHandle[] mCloseHandleMocks;
+ // Array of mocked announcements
+ private Announcement[] mAnnouncementMocks;
+
+ @Before
+ public void setUp() throws Exception {
+ ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor =
+ ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+ when(mListenerMock.asBinder()).thenReturn(mBinderMock);
+
+ mAnnouncementAggregator = new AnnouncementAggregator(mListenerMock, mLock);
+
+ verify(mBinderMock).linkToDeath(deathRecipientCaptor.capture(), anyInt());
+ mDeathRecipient = deathRecipientCaptor.getValue();
+ }
+
+ @Test
+ public void onListUpdated_withOneModuleWatcher() throws Exception {
+ ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+ ArgumentCaptor.forClass(IAnnouncementListener.class);
+ watchModules(/* moduleNumber= */ 1);
+
+ verify(mRadioModuleMocks[0]).addAnnouncementListener(moduleWatcherCaptor.capture(), any());
+
+ moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[0]));
+
+ verify(mListenerMock).onListUpdated(any());
+ }
+
+ @Test
+ public void onListUpdated_withMultipleModuleWatchers() throws Exception {
+ int moduleNumber = 3;
+ watchModules(moduleNumber);
+
+ for (int index = 0; index < moduleNumber; index++) {
+ ArgumentCaptor<IAnnouncementListener> moduleWatcherCaptor =
+ ArgumentCaptor.forClass(IAnnouncementListener.class);
+ ArgumentCaptor<List<Announcement>> announcementsCaptor =
+ ArgumentCaptor.forClass(List.class);
+ verify(mRadioModuleMocks[index])
+ .addAnnouncementListener(moduleWatcherCaptor.capture(), any());
+
+ moduleWatcherCaptor.getValue().onListUpdated(Arrays.asList(mAnnouncementMocks[index]));
+
+ verify(mListenerMock, times(index + 1)).onListUpdated(announcementsCaptor.capture());
+ assertWithMessage("Number of announcements %s", announcementsCaptor.getValue())
+ .that(announcementsCaptor.getValue().size()).isEqualTo(index + 1);
+ }
+ }
+
+ @Test
+ public void close_withOneModuleWatcher_invokesCloseHandle() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mAnnouncementAggregator.close();
+
+ verify(mCloseHandleMocks[0]).close();
+ verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+ }
+
+ @Test
+ public void close_withMultipleModuleWatcher_invokesCloseHandles() throws Exception {
+ int moduleNumber = 3;
+ watchModules(moduleNumber);
+
+ mAnnouncementAggregator.close();
+
+ for (int index = 0; index < moduleNumber; index++) {
+ verify(mCloseHandleMocks[index]).close();
+ }
+ }
+
+ @Test
+ public void close_twice_invokesCloseHandleOnce() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mAnnouncementAggregator.close();
+ mAnnouncementAggregator.close();
+
+ verify(mCloseHandleMocks[0]).close();
+ verify(mBinderMock).unlinkToDeath(eq(mDeathRecipient), anyInt());
+ }
+
+ @Test
+ public void binderDied_forDeathRecipient_invokesCloseHandle() throws Exception {
+ watchModules(/* moduleNumber= */ 1);
+
+ mDeathRecipient.binderDied();
+
+ verify(mCloseHandleMocks[0]).close();
+
+ }
+
+ private void watchModules(int moduleNumber) throws RemoteException {
+ mRadioModuleMocks = new RadioModule[moduleNumber];
+ mCloseHandleMocks = new ICloseHandle[moduleNumber];
+ mAnnouncementMocks = new Announcement[moduleNumber];
+
+ for (int index = 0; index < moduleNumber; index++) {
+ mRadioModuleMocks[index] = mock(RadioModule.class);
+ mCloseHandleMocks[index] = mock(ICloseHandle.class);
+ mAnnouncementMocks[index] = mock(Announcement.class);
+
+ when(mRadioModuleMocks[index].addAnnouncementListener(any(), any()))
+ .thenReturn(mCloseHandleMocks[index]);
+ mAnnouncementAggregator.watchModule(mRadioModuleMocks[index], TEST_ENABLED_TYPES);
+ }
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
new file mode 100644
index 000000000000..31195548c968
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.AmFmBandRange;
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.Properties;
+import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Map;
+
+public final class ConversionUtilsTest {
+
+ private static final int FM_LOWER_LIMIT = 87500;
+ private static final int FM_UPPER_LIMIT = 108000;
+ private static final int FM_SPACING = 200;
+ private static final int AM_LOWER_LIMIT = 540;
+ private static final int AM_UPPER_LIMIT = 1700;
+ private static final int AM_SPACING = 10;
+ private static final String DAB_ENTRY_LABEL_1 = "5A";
+ private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
+ private static final String DAB_ENTRY_LABEL_2 = "12D";
+ private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
+ private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
+ private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
+ private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
+ private static final String VENDOR_INFO_VALUE_2 = "vendorValue2";
+ private static final String TEST_SERVICE_NAME = "serviceMock";
+ private static final int TEST_ID = 1;
+ private static final String TEST_MAKER = "makerMock";
+ private static final String TEST_PRODUCT = "productMock";
+ private static final String TEST_VERSION = "versionMock";
+ private static final String TEST_SERIAL = "serialMock";
+
+ private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
+ private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
+
+ private static final RadioManager.ModuleProperties MODULE_PROPERTIES =
+ convertToModuleProperties();
+ private static final Announcement ANNOUNCEMENT =
+ ConversionUtils.announcementFromHalAnnouncement(
+ AidlTestUtils.makeAnnouncement(TEST_ENABLED_TYPE, TEST_ANNOUNCEMENT_FREQUENCY));
+
+ @Rule
+ public final Expect expect = Expect.create();
+
+ @Test
+ public void propertiesFromHalProperties_idsMatch() {
+ expect.withMessage("Properties id")
+ .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_serviceNamesMatch() {
+ expect.withMessage("Service name")
+ .that(MODULE_PROPERTIES.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_implementorsMatch() {
+ expect.withMessage("Implementor")
+ .that(MODULE_PROPERTIES.getImplementor()).isEqualTo(TEST_MAKER);
+ }
+
+
+ @Test
+ public void propertiesFromHalProperties_productsMatch() {
+ expect.withMessage("Product")
+ .that(MODULE_PROPERTIES.getProduct()).isEqualTo(TEST_PRODUCT);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_versionsMatch() {
+ expect.withMessage("Version")
+ .that(MODULE_PROPERTIES.getVersion()).isEqualTo(TEST_VERSION);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_serialsMatch() {
+ expect.withMessage("Serial")
+ .that(MODULE_PROPERTIES.getSerial()).isEqualTo(TEST_SERIAL);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_dabTableInfoMatch() {
+ Map<String, Integer> dabTableExpected = Map.of(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1,
+ DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2);
+
+ expect.withMessage("Supported program types")
+ .that(MODULE_PROPERTIES.getDabFrequencyTable())
+ .containsExactlyEntriesIn(dabTableExpected);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_vendorInfoMatch() {
+ Map<String, String> vendorInfoExpected = Map.of(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1,
+ VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2);
+
+ expect.withMessage("Vendor info").that(MODULE_PROPERTIES.getVendorInfo())
+ .containsExactlyEntriesIn(vendorInfoExpected);
+ }
+
+ @Test
+ public void propertiesFromHalProperties_bandsMatch() {
+ RadioManager.BandDescriptor[] bands = MODULE_PROPERTIES.getBands();
+
+ expect.withMessage("Band descriptors").that(bands).hasLength(2);
+
+ expect.withMessage("FM band frequency lower limit")
+ .that(bands[0].getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
+ expect.withMessage("FM band frequency upper limit")
+ .that(bands[0].getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
+ expect.withMessage("FM band frequency spacing")
+ .that(bands[0].getSpacing()).isEqualTo(FM_SPACING);
+
+ expect.withMessage("AM band frequency lower limit")
+ .that(bands[1].getLowerLimit()).isEqualTo(AM_LOWER_LIMIT);
+ expect.withMessage("AM band frequency upper limit")
+ .that(bands[1].getUpperLimit()).isEqualTo(AM_UPPER_LIMIT);
+ expect.withMessage("AM band frequency spacing")
+ .that(bands[1].getSpacing()).isEqualTo(AM_SPACING);
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_typesMatch() {
+ expect.withMessage("Announcement type")
+ .that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_selectorsMatch() {
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_ANNOUNCEMENT_FREQUENCY);
+
+ ProgramSelector selector = ANNOUNCEMENT.getSelector();
+
+ expect.withMessage("Primary id of announcement selector")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ expect.withMessage("Secondary ids of announcement selector")
+ .that(selector.getSecondaryIds()).isEmpty();
+ }
+
+ @Test
+ public void announcementFromHalAnnouncement_VendorInfoMatch() {
+ expect.withMessage("Announcement vendor info")
+ .that(ANNOUNCEMENT.getVendorInfo()).isEmpty();
+ }
+
+ private static RadioManager.ModuleProperties convertToModuleProperties() {
+ AmFmRegionConfig amFmConfig = createAmFmRegionConfig();
+ DabTableEntry[] dabTableEntries = new DabTableEntry[]{
+ createDabTableEntry(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1),
+ createDabTableEntry(DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2)};
+ Properties properties = createHalProperties();
+
+ return ConversionUtils.propertiesFromHalProperties(TEST_ID, TEST_SERVICE_NAME, properties,
+ amFmConfig, dabTableEntries);
+ }
+
+ private static AmFmRegionConfig createAmFmRegionConfig() {
+ AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
+ amFmRegionConfig.ranges = new AmFmBandRange[]{
+ createAmFmBandRange(FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING),
+ createAmFmBandRange(AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING)};
+ return amFmRegionConfig;
+ }
+
+ private static AmFmBandRange createAmFmBandRange(int lowerBound, int upperBound, int spacing) {
+ AmFmBandRange bandRange = new AmFmBandRange();
+ bandRange.lowerBound = lowerBound;
+ bandRange.upperBound = upperBound;
+ bandRange.spacing = spacing;
+ bandRange.seekSpacing = bandRange.spacing;
+ return bandRange;
+ }
+
+ private static DabTableEntry createDabTableEntry(String label, int value) {
+ DabTableEntry dabTableEntry = new DabTableEntry();
+ dabTableEntry.label = label;
+ dabTableEntry.frequencyKhz = value;
+ return dabTableEntry;
+ }
+
+ private static Properties createHalProperties() {
+ Properties halProperties = new Properties();
+ halProperties.supportedIdentifierTypes = new int[]{IdentifierType.AMFM_FREQUENCY_KHZ,
+ IdentifierType.RDS_PI, IdentifierType.DAB_SID_EXT};
+ halProperties.maker = TEST_MAKER;
+ halProperties.product = TEST_PRODUCT;
+ halProperties.version = TEST_VERSION;
+ halProperties.serial = TEST_SERIAL;
+ halProperties.vendorInfo = new VendorKeyValue[]{
+ AidlTestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1),
+ AidlTestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2)};
+ return halProperties;
+ }
+}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
index 7f71921ed047..cd1cd7e9439c 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/RadioModuleTest.java
@@ -21,11 +21,16 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
import android.hardware.broadcastradio.IBroadcastRadio;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.IAnnouncementListener;
+import android.hardware.radio.ICloseHandle;
import android.hardware.radio.RadioManager;
import android.os.RemoteException;
@@ -41,13 +46,20 @@ import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public final class RadioModuleTest {
+ private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EVENT;
+
// Mocks
@Mock
private IBroadcastRadio mBroadcastRadioMock;
+ @Mock
+ private IAnnouncementListener mListenerMock;
+ @Mock
+ private android.hardware.broadcastradio.ICloseHandle mHalCloseHandleMock;
private final Object mLock = new Object();
// RadioModule under test
private RadioModule mRadioModule;
+ private android.hardware.broadcastradio.IAnnouncementListener mHalListener;
@Before
public void setup() throws RemoteException {
@@ -62,6 +74,11 @@ public final class RadioModuleTest {
// TODO(b/241118988): test non-null image for getImage method
when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
+ doAnswer(invocation -> {
+ mHalListener = (android.hardware.broadcastradio.IAnnouncementListener) invocation
+ .getArguments()[0];
+ return null;
+ }).when(mBroadcastRadioMock).registerAnnouncementListener(any(), any());
}
@Test
@@ -71,7 +88,7 @@ public final class RadioModuleTest {
}
@Test
- public void setInternalHalCallback_callbackSetInHal() throws RemoteException {
+ public void setInternalHalCallback_callbackSetInHal() throws Exception {
mRadioModule.setInternalHalCallback();
verify(mBroadcastRadioMock).setTunerCallback(any());
@@ -97,4 +114,36 @@ public final class RadioModuleTest {
assertWithMessage("Exception for getting image with invalid ID")
.that(thrown).hasMessageThat().contains("Image ID is missing");
}
+
+ @Test
+ public void addAnnouncementListener_listenerRegistered() throws Exception {
+ mRadioModule.addAnnouncementListener(mListenerMock, new int[]{TEST_ENABLED_TYPE});
+
+ verify(mBroadcastRadioMock)
+ .registerAnnouncementListener(any(), eq(new byte[]{TEST_ENABLED_TYPE}));
+ }
+
+ @Test
+ public void onListUpdate_forAnnouncementListener() throws Exception {
+ android.hardware.broadcastradio.Announcement halAnnouncement =
+ AidlTestUtils.makeAnnouncement(TEST_ENABLED_TYPE, /* selectorFreq= */ 96300);
+ mRadioModule.addAnnouncementListener(mListenerMock, new int[]{TEST_ENABLED_TYPE});
+
+ mHalListener.onListUpdated(
+ new android.hardware.broadcastradio.Announcement[]{halAnnouncement});
+
+ verify(mListenerMock).onListUpdated(any());
+ }
+
+ @Test
+ public void close_forCloseHandle() throws Exception {
+ when(mBroadcastRadioMock.registerAnnouncementListener(any(), any()))
+ .thenReturn(mHalCloseHandleMock);
+ ICloseHandle closeHandle =
+ mRadioModule.addAnnouncementListener(mListenerMock, new int[]{TEST_ENABLED_TYPE});
+
+ closeHandle.close();
+
+ verify(mHalCloseHandleMock).close();
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index 8354ad16dc42..06d7cddf3148 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
@@ -31,17 +32,19 @@ import static org.mockito.Mockito.when;
import android.graphics.Bitmap;
import android.hardware.broadcastradio.IBroadcastRadio;
import android.hardware.broadcastradio.ITunerCallback;
+import android.hardware.broadcastradio.IdentifierType;
import android.hardware.broadcastradio.ProgramInfo;
import android.hardware.broadcastradio.Result;
+import android.hardware.broadcastradio.VendorKeyValue;
import android.hardware.radio.ProgramList;
import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioTuner;
-import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.ArrayMap;
import android.util.ArraySet;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,19 +61,20 @@ import java.util.Map;
*/
@RunWith(MockitoJUnitRunner.class)
public final class TunerSessionTest {
+
private static final VerificationWithTimeout CALLBACK_TIMEOUT =
timeout(/* millis= */ 200);
-
- private final int mSignalQuality = 1;
- private final long mAmfmFrequencySpacing = 500;
- private final long[] mAmfmFrequencyList = {97500, 98100, 99100};
- private final RadioManager.FmBandDescriptor mFmBandDescriptor =
+ private static final int SIGNAL_QUALITY = 1;
+ private static final long AM_FM_FREQUENCY_SPACING = 500;
+ private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+ private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
/* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
/* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
/* ea= */ false);
- private final RadioManager.BandConfig mFmBandConfig =
- new RadioManager.FmBandConfig(mFmBandDescriptor);
+ private static final RadioManager.BandConfig FM_BAND_CONFIG =
+ new RadioManager.FmBandConfig(FM_BAND_DESCRIPTOR);
+ private static final int UNSUPPORTED_CONFIG_FLAG = 0;
// Mocks
@Mock private IBroadcastRadio mBroadcastRadioMock;
@@ -83,13 +87,12 @@ public final class TunerSessionTest {
// Objects created by mRadioModule
private ITunerCallback mHalTunerCallback;
private ProgramInfo mHalCurrentInfo;
- private final int mUnsupportedConfigFlag = 0;
private final ArrayMap<Integer, Boolean> mHalConfigMap = new ArrayMap<>();
private TunerSession[] mTunerSessions;
@Before
- public void setup() throws RemoteException {
+ public void setup() throws Exception {
mRadioModule = new RadioModule(mBroadcastRadioMock, new RadioManager.ModuleProperties(
/* id= */ 0, /* serviceName= */ "", /* classId= */ 0, /* implementor= */ "",
/* product= */ "", /* version= */ "", /* serial= */ "", /* numTuners= */ 0,
@@ -105,48 +108,58 @@ public final class TunerSessionTest {
mRadioModule.setInternalHalCallback();
doAnswer(invocation -> {
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0],
- mSignalQuality);
+ android.hardware.broadcastradio.ProgramSelector halSel =
+ (android.hardware.broadcastradio.ProgramSelector) invocation.getArguments()[0];
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(halSel, SIGNAL_QUALITY);
+ if (halSel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ) {
+ throw new ServiceSpecificException(Result.NOT_SUPPORTED);
+ }
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return null;
+ return Result.OK;
}).when(mBroadcastRadioMock).tune(any());
doAnswer(invocation -> {
if ((boolean) invocation.getArguments()[0]) {
- mHalCurrentInfo.selector.primaryId.value += mAmfmFrequencySpacing;
+ mHalCurrentInfo.selector.primaryId.value += AM_FM_FREQUENCY_SPACING;
} else {
- mHalCurrentInfo.selector.primaryId.value -= mAmfmFrequencySpacing;
+ mHalCurrentInfo.selector.primaryId.value -= AM_FM_FREQUENCY_SPACING;
}
mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return null;
+ return Result.OK;
}).when(mBroadcastRadioMock).step(anyBoolean());
doAnswer(invocation -> {
+ if (mHalCurrentInfo == null) {
+ android.hardware.broadcastradio.ProgramSelector placeHolderSelector =
+ AidlTestUtils.makeHalFmSelector(/* freq= */ 97300);
+
+ mHalTunerCallback.onTuneFailed(Result.TIMEOUT, placeHolderSelector);
+ return Result.OK;
+ }
mHalCurrentInfo.selector.primaryId.value = getSeekFrequency(
mHalCurrentInfo.selector.primaryId.value,
!(boolean) invocation.getArguments()[0]);
mHalCurrentInfo.logicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalCurrentInfo.physicallyTunedTo = mHalCurrentInfo.selector.primaryId;
mHalTunerCallback.onCurrentProgramInfoChanged(mHalCurrentInfo);
- return null;
+ return Result.OK;
}).when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
when(mBroadcastRadioMock.getImage(anyInt())).thenReturn(null);
- mHalConfigMap.clear();
doAnswer(invocation -> {
int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == mUnsupportedConfigFlag) {
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
throw new ServiceSpecificException(Result.NOT_SUPPORTED);
}
return mHalConfigMap.getOrDefault(configFlag, false);
}).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
doAnswer(invocation -> {
int configFlag = (int) invocation.getArguments()[0];
- if (configFlag == mUnsupportedConfigFlag) {
+ if (configFlag == UNSUPPORTED_CONFIG_FLAG) {
throw new ServiceSpecificException(Result.NOT_SUPPORTED);
}
mHalConfigMap.put(configFlag, (boolean) invocation.getArguments()[1]);
@@ -154,8 +167,13 @@ public final class TunerSessionTest {
}).when(mBroadcastRadioMock).setConfigFlag(anyInt(), anyBoolean());
}
+ @After
+ public void cleanUp() {
+ mHalConfigMap.clear();
+ }
+
@Test
- public void openSession_withMultipleSessions() throws RemoteException {
+ public void openSession_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
@@ -167,27 +185,27 @@ public final class TunerSessionTest {
}
@Test
- public void setConfiguration() throws RemoteException {
+ public void setConfiguration() throws Exception {
openAidlClients(/* numClients= */ 1);
- mTunerSessions[0].setConfiguration(mFmBandConfig);
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
- verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(mFmBandConfig);
+ verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT).onConfigurationChanged(FM_BAND_CONFIG);
}
@Test
- public void getConfiguration() throws RemoteException {
+ public void getConfiguration() throws Exception {
openAidlClients(/* numClients= */ 1);
- mTunerSessions[0].setConfiguration(mFmBandConfig);
+ mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
RadioManager.BandConfig config = mTunerSessions[0].getConfiguration();
assertWithMessage("Session configuration").that(config)
- .isEqualTo(mFmBandConfig);
+ .isEqualTo(FM_BAND_CONFIG);
}
@Test
- public void setMuted_withUnmuted() throws RemoteException {
+ public void setMuted_withUnmuted() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setMuted(/* mute= */ false);
@@ -197,7 +215,7 @@ public final class TunerSessionTest {
}
@Test
- public void setMuted_withMuted() throws RemoteException {
+ public void setMuted_withMuted() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].setMuted(/* mute= */ true);
@@ -207,7 +225,7 @@ public final class TunerSessionTest {
}
@Test
- public void close_withOneSession() throws RemoteException {
+ public void close_withOneSession() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].close();
@@ -217,7 +235,7 @@ public final class TunerSessionTest {
}
@Test
- public void close_withOnlyOneSession_withMultipleSessions() throws RemoteException {
+ public void close_withOnlyOneSession_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
int closeIdx = 0;
@@ -238,7 +256,7 @@ public final class TunerSessionTest {
}
@Test
- public void close_withOneSession_withError() throws RemoteException {
+ public void close_withOneSession_withError() throws Exception {
openAidlClients(/* numClients= */ 1);
int errorCode = RadioTuner.ERROR_SERVER_DIED;
@@ -250,7 +268,7 @@ public final class TunerSessionTest {
}
@Test
- public void closeSessions_withMultipleSessions_withError() throws RemoteException {
+ public void closeSessions_withMultipleSessions_withError() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
@@ -265,11 +283,11 @@ public final class TunerSessionTest {
}
@Test
- public void tune_withOneSession() throws RemoteException {
+ public void tune_withOneSession() throws Exception {
openAidlClients(/* numClients= */ 1);
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
RadioManager.ProgramInfo tuneInfo =
- AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+ AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
mTunerSessions[0].tune(initialSel);
@@ -277,12 +295,12 @@ public final class TunerSessionTest {
}
@Test
- public void tune_withMultipleSessions() throws RemoteException {
+ public void tune_withMultipleSessions() throws Exception {
int numSessions = 3;
openAidlClients(numSessions);
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
RadioManager.ProgramInfo tuneInfo =
- AidlTestUtils.makeProgramInfo(initialSel, mSignalQuality);
+ AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY);
mTunerSessions[0].tune(initialSel);
@@ -293,15 +311,29 @@ public final class TunerSessionTest {
}
@Test
- public void step_withDirectionUp() throws RemoteException {
- long initFreq = mAmfmFrequencyList[1];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ public void tune_withUnsupportedSelector_throwsException() throws Exception {
+ openAidlClients(/* numClients= */ 1);
+ ProgramSelector unsupportedSelector = AidlTestUtils.makeProgramSelector(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
+
+ UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
+ () -> mTunerSessions[0].tune(unsupportedSelector));
+
+ assertWithMessage("Exception for tuning on unsupported program selector")
+ .that(thrown).hasMessageThat().contains("tune: NOT_SUPPORTED");
+ }
+
+ @Test
+ public void step_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo stepUpInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(initFreq + mAmfmFrequencySpacing),
- mSignalQuality);
+ AidlTestUtils.makeFmSelector(initFreq + AM_FM_FREQUENCY_SPACING),
+ SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].step(/* directionDown= */ false, /* skipSubChannel= */ false);
@@ -310,15 +342,15 @@ public final class TunerSessionTest {
}
@Test
- public void step_withDirectionDown() throws RemoteException {
- long initFreq = mAmfmFrequencyList[1];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ public void step_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[1];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo stepDownInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(initFreq - mAmfmFrequencySpacing),
- mSignalQuality);
+ AidlTestUtils.makeFmSelector(initFreq - AM_FM_FREQUENCY_SPACING),
+ SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
@@ -327,15 +359,15 @@ public final class TunerSessionTest {
}
@Test
- public void scan_withDirectionUp() throws RemoteException {
- long initFreq = mAmfmFrequencyList[2];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ public void scan_withDirectionUp() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
- mSignalQuality);
+ AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ false)),
+ SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
@@ -344,15 +376,28 @@ public final class TunerSessionTest {
}
@Test
- public void scan_withDirectionDown() throws RemoteException {
- long initFreq = mAmfmFrequencyList[2];
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(initFreq);
+ public void scan_callsOnTuneFailedWhenTimeout() throws Exception {
+ int numSessions = 2;
+ openAidlClients(numSessions);
+
+ mTunerSessions[0].scan(/* directionDown= */ false, /* skipSubChannel= */ false);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onTuneFailed(eq(Result.TIMEOUT), any());
+ }
+ }
+
+ @Test
+ public void scan_withDirectionDown() throws Exception {
+ long initFreq = AM_FM_FREQUENCY_LIST[2];
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
RadioManager.ProgramInfo scanUpInfo = AidlTestUtils.makeProgramInfo(
- AidlTestUtils.makeFMSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
- mSignalQuality);
+ AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+ SIGNAL_QUALITY);
openAidlClients(/* numClients= */ 1);
- mHalCurrentInfo = AidlTestUtils.makeHalProgramSelector(
- ConversionUtils.programSelectorToHalProgramSelector(initialSel), mSignalQuality);
+ mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+ ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
mTunerSessions[0].scan(/* directionDown= */ true, /* skipSubChannel= */ false);
verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
@@ -360,9 +405,9 @@ public final class TunerSessionTest {
}
@Test
- public void cancel() throws RemoteException {
+ public void cancel() throws Exception {
openAidlClients(/* numClients= */ 1);
- ProgramSelector initialSel = AidlTestUtils.makeFMSelector(mAmfmFrequencyList[1]);
+ ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
mTunerSessions[0].tune(initialSel);
mTunerSessions[0].cancel();
@@ -371,7 +416,7 @@ public final class TunerSessionTest {
}
@Test
- public void getImage_withInvalidId_throwsIllegalArgumentException() throws RemoteException {
+ public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = IBroadcastRadio.INVALID_IMAGE;
@@ -384,7 +429,7 @@ public final class TunerSessionTest {
}
@Test
- public void getImage_withValidId() throws RemoteException {
+ public void getImage_withValidId() throws Exception {
openAidlClients(/* numClients= */ 1);
int imageId = 1;
@@ -394,7 +439,7 @@ public final class TunerSessionTest {
}
@Test
- public void startBackgroundScan() throws RemoteException {
+ public void startBackgroundScan() throws Exception {
openAidlClients(/* numClients= */ 1);
mTunerSessions[0].startBackgroundScan();
@@ -403,7 +448,7 @@ public final class TunerSessionTest {
}
@Test
- public void stopProgramListUpdates() throws RemoteException {
+ public void stopProgramListUpdates() throws Exception {
openAidlClients(/* numClients= */ 1);
ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
/* includeCategories= */ true, /* excludeModifications= */ false);
@@ -415,9 +460,9 @@ public final class TunerSessionTest {
}
@Test
- public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws RemoteException {
+ public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag;
+ int flag = UNSUPPORTED_CONFIG_FLAG;
boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
@@ -426,9 +471,9 @@ public final class TunerSessionTest {
}
@Test
- public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws RemoteException {
+ public void isConfigFlagSupported_withSupportedFlag_returnsTrue() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
boolean isSupported = mTunerSessions[0].isConfigFlagSupported(flag);
@@ -437,9 +482,9 @@ public final class TunerSessionTest {
}
@Test
- public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws RemoteException {
+ public void setConfigFlag_withUnsupportedFlag_throwsRuntimeException() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag;
+ int flag = UNSUPPORTED_CONFIG_FLAG;
RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
@@ -450,9 +495,9 @@ public final class TunerSessionTest {
}
@Test
- public void setConfigFlag_withFlagSetToTrue() throws RemoteException {
+ public void setConfigFlag_withFlagSetToTrue() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
@@ -460,9 +505,9 @@ public final class TunerSessionTest {
}
@Test
- public void setConfigFlag_withFlagSetToFalse() throws RemoteException {
+ public void setConfigFlag_withFlagSetToFalse() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
mTunerSessions[0].setConfigFlag(flag, /* value= */ false);
@@ -471,9 +516,9 @@ public final class TunerSessionTest {
@Test
public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
- throws RemoteException {
+ throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag;
+ int flag = UNSUPPORTED_CONFIG_FLAG;
RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
mTunerSessions[0].isConfigFlagSet(flag);
@@ -484,9 +529,9 @@ public final class TunerSessionTest {
}
@Test
- public void isConfigFlagSet_withSupportedFlag() throws RemoteException {
+ public void isConfigFlagSet_withSupportedFlag() throws Exception {
openAidlClients(/* numClients= */ 1);
- int flag = mUnsupportedConfigFlag + 1;
+ int flag = UNSUPPORTED_CONFIG_FLAG + 1;
boolean expectedConfigFlagValue = true;
mTunerSessions[0].setConfigFlag(flag, /* value= */ expectedConfigFlagValue);
@@ -497,11 +542,10 @@ public final class TunerSessionTest {
}
@Test
- public void setParameters_withMockParameters() throws RemoteException {
+ public void setParameters_withMockParameters() throws Exception {
openAidlClients(/* numClients= */ 1);
- Map<String, String> parametersSet = new ArrayMap<>();
- parametersSet.put("mockParam1", "mockValue1");
- parametersSet.put("mockParam2", "mockValue2");
+ Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+ "mockParam2", "mockValue2");
mTunerSessions[0].setParameters(parametersSet);
@@ -510,7 +554,7 @@ public final class TunerSessionTest {
}
@Test
- public void getParameters_withMockKeys() throws RemoteException {
+ public void getParameters_withMockKeys() throws Exception {
openAidlClients(/* numClients= */ 1);
List<String> parameterKeys = new ArrayList<>(2);
parameterKeys.add("mockKey1");
@@ -522,7 +566,36 @@ public final class TunerSessionTest {
parameterKeys.toArray(new String[0]));
}
- private void openAidlClients(int numClients) throws RemoteException {
+ @Test
+ public void onConfigFlagUpdated_forTunerCallback() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+
+ mHalTunerCallback.onAntennaStateChange(/* connected= */ false);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onAntennaState(/* connected= */ false);
+ }
+ }
+
+ @Test
+ public void onParametersUpdated_forTunerCallback() throws Exception {
+ int numSessions = 3;
+ openAidlClients(numSessions);
+ VendorKeyValue[] parametersUpdates = {
+ AidlTestUtils.makeVendorKeyValue("com.vendor.parameter1", "value1")};
+ Map<String, String> parametersExpected = Map.of("com.vendor.parameter1", "value1");
+
+ mHalTunerCallback.onParametersUpdated(parametersUpdates);
+
+ for (int index = 0; index < numSessions; index++) {
+ verify(mAidlTunerCallbackMocks[index], CALLBACK_TIMEOUT)
+ .onParametersUpdated(parametersExpected);
+ }
+ }
+
+ private void openAidlClients(int numClients) throws Exception {
mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients];
mTunerSessions = new TunerSession[numClients];
for (int index = 0; index < numClients; index++) {
@@ -534,18 +607,18 @@ public final class TunerSessionTest {
private long getSeekFrequency(long currentFrequency, boolean seekDown) {
long seekFrequency;
if (seekDown) {
- seekFrequency = mAmfmFrequencyList[mAmfmFrequencyList.length - 1];
- for (int i = mAmfmFrequencyList.length - 1; i >= 0; i--) {
- if (mAmfmFrequencyList[i] < currentFrequency) {
- seekFrequency = mAmfmFrequencyList[i];
+ seekFrequency = AM_FM_FREQUENCY_LIST[AM_FM_FREQUENCY_LIST.length - 1];
+ for (int i = AM_FM_FREQUENCY_LIST.length - 1; i >= 0; i--) {
+ if (AM_FM_FREQUENCY_LIST[i] < currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[i];
break;
}
}
} else {
- seekFrequency = mAmfmFrequencyList[0];
- for (int index = 0; index < mAmfmFrequencyList.length; index++) {
- if (mAmfmFrequencyList[index] > currentFrequency) {
- seekFrequency = mAmfmFrequencyList[index];
+ seekFrequency = AM_FM_FREQUENCY_LIST[0];
+ for (int index = 0; index < AM_FM_FREQUENCY_LIST.length; index++) {
+ if (AM_FM_FREQUENCY_LIST[index] > currentFrequency) {
+ seekFrequency = AM_FM_FREQUENCY_LIST[index];
break;
}
}
diff --git a/core/tests/coretests/OWNERS b/core/tests/coretests/OWNERS
index 0fb0c3021486..e8c9fe70272d 100644
--- a/core/tests/coretests/OWNERS
+++ b/core/tests/coretests/OWNERS
@@ -1 +1,4 @@
include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
+
+per-file BinderTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
+per-file ParcelTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
new file mode 100644
index 000000000000..67b24ec17a27
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -0,0 +1,277 @@
+/*
+ * 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 android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
+import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupRestoreEventLoggerTest {
+ private static final int DATA_TYPES_ALLOWED = 15;
+
+ private static final String DATA_TYPE_1 = "data_type_1";
+ private static final String DATA_TYPE_2 = "data_type_2";
+ private static final String ERROR_1 = "error_1";
+ private static final String ERROR_2 = "error_2";
+ private static final String METADATA_1 = "metadata_1";
+ private static final String METADATA_2 = "metadata_2";
+
+ private BackupRestoreEventLogger mLogger;
+ private MessageDigest mHashDigest;
+
+ @Before
+ public void setUp() throws Exception {
+ mHashDigest = MessageDigest.getInstance("SHA-256");
+ }
+
+ @Test
+ public void testBackupLogger_rejectsRestoreLogs() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ }
+
+ @Test
+ public void testRestoreLogger_rejectsBackupLogs() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+ assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+ }
+
+ @Test
+ public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
+ assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
+ .isTrue();
+ assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+ }
+
+ assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+ .isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+ String dataType = DATA_TYPE_1 + i;
+ assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
+ assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
+ .isTrue();
+ assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+ }
+
+ assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
+ assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+ .isFalse();
+ assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+ assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+ }
+
+ @Test
+ public void testLogBackupMetadata_repeatedCalls_recordsLatestMetadataHash() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_1);
+ mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_2);
+
+ byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+ byte[] expectedHash = getMetaDataHash(METADATA_2);
+ assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+ }
+
+ @Test
+ public void testLogRestoreMetadata_repeatedCalls_recordsLatestMetadataHash() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_2);
+
+ byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+ byte[] expectedHash = getMetaDataHash(METADATA_2);
+ assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+ }
+
+ @Test
+ public void testLogItemsBackedUp_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+ mLogger.logItemsBackedUp(DATA_TYPE_1, secondCount);
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestored_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+ mLogger.logItemsRestored(DATA_TYPE_1, secondCount);
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackedUp_multipleDataTypes_recordsEachDataType() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+ mLogger.logItemsBackedUp(DATA_TYPE_2, secondCount);
+
+ int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+ assertThat(firstDataTypeCount).isEqualTo(firstCount);
+ assertThat(secondDataTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestored_multipleDataTypes_recordsEachDataType() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+ mLogger.logItemsRestored(DATA_TYPE_2, secondCount);
+
+ int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+ int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+ assertThat(firstDataTypeCount).isEqualTo(firstCount);
+ assertThat(secondDataTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackupFailed_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, /* error */ null);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, "error");
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestoreFailed_repeatedCalls_recordsTotalItems() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, /* error */ null);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, "error");
+
+ int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+ assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+ }
+
+ @Test
+ public void testLogItemsBackupFailed_multipleErrors_recordsEachError() {
+ mLogger = new BackupRestoreEventLogger(BACKUP);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
+ mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+ int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_1);
+ int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_2);
+ assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+ assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+ }
+
+ @Test
+ public void testLogItemsRestoreFailed_multipleErrors_recordsEachError() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+
+ int firstCount = 10;
+ int secondCount = 5;
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+ int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_1);
+ int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+ .getErrors().get(ERROR_2);
+ assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+ assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+ }
+
+ private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
+ @BackupRestoreDataType String dataType) {
+ Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
+ if (result.isEmpty()) {
+ fail("Failed to find result for data type: " + dataType);
+ }
+ return result.get();
+ }
+
+ private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
+ BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+ List<DataTypeResult> resultList = logger.getLoggingResults();
+ return resultList.stream().filter(
+ dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
+ }
+
+ private byte[] getMetaDataHash(String metaData) {
+ return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/backup/OWNERS b/core/tests/coretests/src/android/app/backup/OWNERS
new file mode 100644
index 000000000000..53b6c78b3895
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS \ No newline at end of file
diff --git a/core/tests/coretests/src/android/os/BundleMergerTest.java b/core/tests/coretests/src/android/os/BundleMergerTest.java
new file mode 100644
index 000000000000..b7012ba66124
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BundleMergerTest.java
@@ -0,0 +1,408 @@
+/*
+ * 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.os;
+
+import static android.os.BundleMerger.STRATEGY_ARRAY_APPEND;
+import static android.os.BundleMerger.STRATEGY_ARRAY_LIST_APPEND;
+import static android.os.BundleMerger.STRATEGY_BOOLEAN_AND;
+import static android.os.BundleMerger.STRATEGY_BOOLEAN_OR;
+import static android.os.BundleMerger.STRATEGY_COMPARABLE_MAX;
+import static android.os.BundleMerger.STRATEGY_COMPARABLE_MIN;
+import static android.os.BundleMerger.STRATEGY_FIRST;
+import static android.os.BundleMerger.STRATEGY_LAST;
+import static android.os.BundleMerger.STRATEGY_NUMBER_ADD;
+import static android.os.BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST;
+import static android.os.BundleMerger.STRATEGY_REJECT;
+import static android.os.BundleMerger.merge;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.content.Intent;
+import android.net.Uri;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class BundleMergerTest {
+ /**
+ * Strategies are only applied when there is an actual conflict; in the
+ * absence of conflict we pick whichever value is defined.
+ */
+ @Test
+ public void testNoConflict() throws Exception {
+ for (int strategy = Byte.MIN_VALUE; strategy < Byte.MAX_VALUE; strategy++) {
+ assertEquals(null, merge(strategy, null, null));
+ assertEquals(10, merge(strategy, 10, null));
+ assertEquals(20, merge(strategy, null, 20));
+ }
+ }
+
+ /**
+ * Strategies are only applied to identical data types; if there are mixed
+ * types we always reject the two conflicting values.
+ */
+ @Test
+ public void testMixedTypes() throws Exception {
+ for (int strategy = Byte.MIN_VALUE; strategy < Byte.MAX_VALUE; strategy++) {
+ final int finalStrategy = strategy;
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, 10, "foo");
+ });
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, List.of("foo"), "bar");
+ });
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, new String[] { "foo" }, "bar");
+ });
+ assertThrows(Exception.class, () -> {
+ merge(finalStrategy, Integer.valueOf(10), Long.valueOf(10));
+ });
+ }
+ }
+
+ @Test
+ public void testStrategyReject() throws Exception {
+ assertEquals(null, merge(STRATEGY_REJECT, 10, 20));
+
+ // Identical values aren't technically a conflict, so they're passed
+ // through without being rejected
+ assertEquals(10, merge(STRATEGY_REJECT, 10, 10));
+ assertArrayEquals(new int[] {10},
+ (int[]) merge(STRATEGY_REJECT, new int[] {10}, new int[] {10}));
+ }
+
+ @Test
+ public void testStrategyFirst() throws Exception {
+ assertEquals(10, merge(STRATEGY_FIRST, 10, 20));
+ }
+
+ @Test
+ public void testStrategyLast() throws Exception {
+ assertEquals(20, merge(STRATEGY_LAST, 10, 20));
+ }
+
+ @Test
+ public void testStrategyComparableMin() throws Exception {
+ assertEquals(10, merge(STRATEGY_COMPARABLE_MIN, 10, 20));
+ assertEquals(10, merge(STRATEGY_COMPARABLE_MIN, 20, 10));
+ assertEquals("a", merge(STRATEGY_COMPARABLE_MIN, "a", "z"));
+ assertEquals("a", merge(STRATEGY_COMPARABLE_MIN, "z", "a"));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_COMPARABLE_MIN, new Binder(), new Binder());
+ });
+ }
+
+ @Test
+ public void testStrategyComparableMax() throws Exception {
+ assertEquals(20, merge(STRATEGY_COMPARABLE_MAX, 10, 20));
+ assertEquals(20, merge(STRATEGY_COMPARABLE_MAX, 20, 10));
+ assertEquals("z", merge(STRATEGY_COMPARABLE_MAX, "a", "z"));
+ assertEquals("z", merge(STRATEGY_COMPARABLE_MAX, "z", "a"));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_COMPARABLE_MAX, new Binder(), new Binder());
+ });
+ }
+
+ @Test
+ public void testStrategyNumberAdd() throws Exception {
+ assertEquals(30, merge(STRATEGY_NUMBER_ADD, 10, 20));
+ assertEquals(30, merge(STRATEGY_NUMBER_ADD, 20, 10));
+ assertEquals(30L, merge(STRATEGY_NUMBER_ADD, 10L, 20L));
+ assertEquals(30L, merge(STRATEGY_NUMBER_ADD, 20L, 10L));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_NUMBER_ADD, new Binder(), new Binder());
+ });
+ }
+
+ @Test
+ public void testStrategyNumberIncrementFirst() throws Exception {
+ assertEquals(11, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 10, 20));
+ assertEquals(21, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 20, 10));
+ assertEquals(11L, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 10L, 20L));
+ assertEquals(21L, merge(STRATEGY_NUMBER_INCREMENT_FIRST, 20L, 10L));
+ }
+
+ @Test
+ public void testStrategyBooleanAnd() throws Exception {
+ assertEquals(false, merge(STRATEGY_BOOLEAN_AND, false, false));
+ assertEquals(false, merge(STRATEGY_BOOLEAN_AND, true, false));
+ assertEquals(false, merge(STRATEGY_BOOLEAN_AND, false, true));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_AND, true, true));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_BOOLEAN_AND, "True!", "False?");
+ });
+ }
+
+ @Test
+ public void testStrategyBooleanOr() throws Exception {
+ assertEquals(false, merge(STRATEGY_BOOLEAN_OR, false, false));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_OR, true, false));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_OR, false, true));
+ assertEquals(true, merge(STRATEGY_BOOLEAN_OR, true, true));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_BOOLEAN_OR, "True!", "False?");
+ });
+ }
+
+ @Test
+ public void testStrategyArrayAppend() throws Exception {
+ assertArrayEquals(new int[] {},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {}, new int[] {}));
+ assertArrayEquals(new int[] {10},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10}, new int[] {}));
+ assertArrayEquals(new int[] {20},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {}, new int[] {20}));
+ assertArrayEquals(new int[] {10, 20},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10}, new int[] {20}));
+ assertArrayEquals(new int[] {10, 30, 20, 40},
+ (int[]) merge(STRATEGY_ARRAY_APPEND, new int[] {10, 30}, new int[] {20, 40}));
+ assertArrayEquals(new String[] {"a", "b"},
+ (String[]) merge(STRATEGY_ARRAY_APPEND, new String[] {"a"}, new String[] {"b"}));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_ARRAY_APPEND, 10, 20);
+ });
+ }
+
+ @Test
+ public void testStrategyArrayListAppend() throws Exception {
+ assertEquals(arrayListOf(),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf()));
+ assertEquals(arrayListOf(10),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10), arrayListOf()));
+ assertEquals(arrayListOf(20),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(), arrayListOf(20)));
+ assertEquals(arrayListOf(10, 20),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10), arrayListOf(20)));
+ assertEquals(arrayListOf(10, 30, 20, 40),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf(10, 30), arrayListOf(20, 40)));
+ assertEquals(arrayListOf("a", "b"),
+ merge(STRATEGY_ARRAY_LIST_APPEND, arrayListOf("a"), arrayListOf("b")));
+
+ assertThrows(Exception.class, () -> {
+ merge(STRATEGY_ARRAY_LIST_APPEND, 10, 20);
+ });
+ }
+
+ @Test
+ public void testMerge_Simple() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ final Bundle probe = new Bundle();
+ probe.putInt(Intent.EXTRA_INDEX, 42);
+
+ assertEquals(null, merger.merge(null, null));
+ assertEquals(probe.keySet(), merger.merge(probe, null).keySet());
+ assertEquals(probe.keySet(), merger.merge(null, probe).keySet());
+ assertEquals(probe.keySet(), merger.merge(probe, probe).keySet());
+ }
+
+ /**
+ * Verify that we can merge parcelables present in the base classpath, since
+ * everyone on the device will be able to unpack them.
+ */
+ @Test
+ public void testMerge_Parcelable_BCP() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(Intent.EXTRA_STREAM, STRATEGY_COMPARABLE_MIN);
+
+ Bundle a = new Bundle();
+ a.putParcelable(Intent.EXTRA_STREAM, Uri.parse("http://example.com"));
+ a = parcelAndUnparcel(a);
+
+ Bundle b = new Bundle();
+ b.putParcelable(Intent.EXTRA_STREAM, Uri.parse("http://example.net"));
+ b = parcelAndUnparcel(b);
+
+ assertEquals(Uri.parse("http://example.com"),
+ merger.merge(a, b).getParcelable(Intent.EXTRA_STREAM, Uri.class));
+ assertEquals(Uri.parse("http://example.com"),
+ merger.merge(b, a).getParcelable(Intent.EXTRA_STREAM, Uri.class));
+ }
+
+ /**
+ * Verify that we tiptoe around custom parcelables while still merging other
+ * known data types. Custom parcelables aren't in the base classpath, so not
+ * everyone on the device will be able to unpack them.
+ */
+ @Test
+ public void testMerge_Parcelable_Custom() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(Intent.EXTRA_INDEX, STRATEGY_NUMBER_ADD);
+
+ Bundle a = new Bundle();
+ a.putInt(Intent.EXTRA_INDEX, 10);
+ a.putString(Intent.EXTRA_CC, "foo@bar.com");
+ a.putParcelable(Intent.EXTRA_SUBJECT, new ExplodingParcelable());
+ a = parcelAndUnparcel(a);
+
+ Bundle b = new Bundle();
+ b.putInt(Intent.EXTRA_INDEX, 20);
+ a.putString(Intent.EXTRA_BCC, "foo@baz.com");
+ b.putParcelable(Intent.EXTRA_STREAM, new ExplodingParcelable());
+ b = parcelAndUnparcel(b);
+
+ Bundle ab = merger.merge(a, b);
+ assertEquals(Set.of(Intent.EXTRA_INDEX, Intent.EXTRA_CC, Intent.EXTRA_BCC,
+ Intent.EXTRA_SUBJECT, Intent.EXTRA_STREAM), ab.keySet());
+ assertEquals(30, ab.getInt(Intent.EXTRA_INDEX));
+ assertEquals("foo@bar.com", ab.getString(Intent.EXTRA_CC));
+ assertEquals("foo@baz.com", ab.getString(Intent.EXTRA_BCC));
+
+ // And finally, make sure that if we try unpacking one of our custom
+ // values that we actually explode
+ assertThrows(BadParcelableException.class, () -> {
+ ab.getParcelable(Intent.EXTRA_SUBJECT, ExplodingParcelable.class);
+ });
+ assertThrows(BadParcelableException.class, () -> {
+ ab.getParcelable(Intent.EXTRA_STREAM, ExplodingParcelable.class);
+ });
+ }
+
+ @Test
+ public void testMerge_PackageChanged() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, STRATEGY_ARRAY_APPEND);
+
+ final Bundle first = new Bundle();
+ first.putInt(Intent.EXTRA_UID, 10001);
+ first.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, new String[] {
+ "com.example.Foo",
+ });
+
+ final Bundle second = new Bundle();
+ second.putInt(Intent.EXTRA_UID, 10001);
+ second.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, new String[] {
+ "com.example.Bar",
+ "com.example.Baz",
+ });
+
+ final Bundle res = merger.merge(first, second);
+ assertEquals(10001, res.getInt(Intent.EXTRA_UID));
+ assertArrayEquals(new String[] {
+ "com.example.Foo", "com.example.Bar", "com.example.Baz",
+ }, res.getStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST));
+ }
+
+ /**
+ * Each event in isolation reports "zero events dropped", but if we need to
+ * merge them together, then we start incrementing.
+ */
+ @Test
+ public void testMerge_DropBox() throws Exception {
+ final BundleMerger merger = new BundleMerger();
+ merger.setMergeStrategy(DropBoxManager.EXTRA_TIME,
+ STRATEGY_COMPARABLE_MAX);
+ merger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT,
+ STRATEGY_NUMBER_INCREMENT_FIRST);
+
+ final long now = System.currentTimeMillis();
+ final Bundle a = new Bundle();
+ a.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
+ a.putLong(DropBoxManager.EXTRA_TIME, now);
+ a.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
+
+ final Bundle b = new Bundle();
+ b.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
+ b.putLong(DropBoxManager.EXTRA_TIME, now + 1000);
+ b.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
+
+ final Bundle c = new Bundle();
+ c.putString(DropBoxManager.EXTRA_TAG, "system_server_strictmode");
+ c.putLong(DropBoxManager.EXTRA_TIME, now + 2000);
+ c.putInt(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
+
+ final Bundle ab = merger.merge(a, b);
+ assertEquals("system_server_strictmode", ab.getString(DropBoxManager.EXTRA_TAG));
+ assertEquals(now + 1000, ab.getLong(DropBoxManager.EXTRA_TIME));
+ assertEquals(1, ab.getInt(DropBoxManager.EXTRA_DROPPED_COUNT));
+
+ final Bundle abc = merger.merge(ab, c);
+ assertEquals("system_server_strictmode", abc.getString(DropBoxManager.EXTRA_TAG));
+ assertEquals(now + 2000, abc.getLong(DropBoxManager.EXTRA_TIME));
+ assertEquals(2, abc.getInt(DropBoxManager.EXTRA_DROPPED_COUNT));
+ }
+
+ private static ArrayList<Object> arrayListOf(Object... values) {
+ final ArrayList<Object> res = new ArrayList<>(values.length);
+ for (Object value : values) {
+ res.add(value);
+ }
+ return res;
+ }
+
+ private static Bundle parcelAndUnparcel(Bundle input) {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ input.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return Bundle.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ /**
+ * Object that only offers to parcel itself; if something tries unparceling
+ * it, it will "explode" by throwing an exception.
+ * <p>
+ * Useful for verifying interactions that must leave unknown data in a
+ * parceled state.
+ */
+ public static class ExplodingParcelable implements Parcelable {
+ public ExplodingParcelable() {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(42);
+ }
+
+ public static final Creator<ExplodingParcelable> CREATOR =
+ new Creator<ExplodingParcelable>() {
+ @Override
+ public ExplodingParcelable createFromParcel(Parcel in) {
+ throw new BadParcelableException("exploding!");
+ }
+
+ @Override
+ public ExplodingParcelable[] newArray(int size) {
+ throw new BadParcelableException("exploding!");
+ }
+ };
+ }
+}
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index fdd278b9c621..e2fe87b4cfe3 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -37,6 +37,13 @@ public class ParcelTest {
private static final String INTERFACE_TOKEN_2 = "Another IBinder interface token";
@Test
+ public void testIsForRpc() {
+ Parcel p = Parcel.obtain();
+ assertEquals(false, p.isForRpc());
+ p.recycle();
+ }
+
+ @Test
public void testCallingWorkSourceUidAfterWrite() {
Parcel p = Parcel.obtain();
// Method does not throw if replaceCallingWorkSourceUid is called before requests headers
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index ddae652cec05..19c2c6153558 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -193,11 +193,15 @@ public class BackspaceTest {
backspace(state, 0);
state.assertEquals("|");
- // Emoji modifier can be appended to the first emoji.
+ // Emoji modifier can be appended to each emoji.
state.setByString("U+1F469 U+1F3FB U+200D U+1F4BC |");
backspace(state, 0);
state.assertEquals("|");
+ state.setByString("U+1F468 U+1F3FF U+200D U+2764 U+FE0F U+200D U+1F468 U+1F3FB |");
+ backspace(state, 0);
+ state.assertEquals("|");
+
// End with ZERO WIDTH JOINER
state.setByString("U+1F441 U+200D |");
backspace(state, 0);
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index cc68fcee9f32..5e12313e4136 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -31,9 +31,12 @@ import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.LAST_TYPE;
import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.defaultVisible;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -63,7 +66,7 @@ import android.os.CancellationSignal;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
-import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
@@ -245,7 +248,7 @@ public class InsetsControllerTest {
mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(Type.ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */);
verify(loggingListener).onReady(notNull(), anyInt());
});
}
@@ -260,16 +263,16 @@ public class InsetsControllerTest {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
// since there is no focused view, forcefully make IME visible.
- mController.show(Type.ime(), true /* fromIme */);
- mController.show(Type.all());
+ mController.show(ime(), true /* fromIme */);
+ mController.show(all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.ime(), true /* fromIme */);
- mController.hide(Type.all());
+ mController.hide(ime(), true /* fromIme */);
+ mController.hide(all());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
@@ -285,10 +288,10 @@ public class InsetsControllerTest {
mController.onControlsChanged(new InsetsSourceControl[] { ime });
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
- mController.show(Type.ime(), true /* fromIme */);
+ mController.show(ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.ime(), true /* fromIme */);
+ mController.hide(ime(), true /* fromIme */);
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
@@ -304,7 +307,7 @@ public class InsetsControllerTest {
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// test hide select types.
mController.hide(types);
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
@@ -336,7 +339,7 @@ public class InsetsControllerTest {
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// test show select types.
mController.show(types);
mController.cancelExistingAnimations();
@@ -345,21 +348,21 @@ public class InsetsControllerTest {
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test hide all
- mController.hide(Type.all());
+ mController.hide(all());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test single show
- mController.show(Type.navigationBars());
+ mController.show(navigationBars());
mController.cancelExistingAnimations();
assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test single hide
- mController.hide(Type.navigationBars());
+ mController.hide(navigationBars());
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
@@ -377,8 +380,8 @@ public class InsetsControllerTest {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// start two animations and see if previous is cancelled and final state is reached.
- mController.hide(Type.navigationBars());
- mController.hide(Type.systemBars());
+ mController.hide(navigationBars());
+ mController.hide(systemBars());
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -386,8 +389,8 @@ public class InsetsControllerTest {
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.show(Type.navigationBars());
- mController.show(Type.systemBars());
+ mController.show(navigationBars());
+ mController.show(systemBars());
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -395,10 +398,10 @@ public class InsetsControllerTest {
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// show two at a time and hide one by one.
mController.show(types);
- mController.hide(Type.navigationBars());
+ mController.hide(navigationBars());
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -406,7 +409,7 @@ public class InsetsControllerTest {
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.systemBars());
+ mController.hide(systemBars());
assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimations();
@@ -425,16 +428,16 @@ public class InsetsControllerTest {
InsetsSourceControl ime = controls[2];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- int types = Type.navigationBars() | Type.systemBars();
+ int types = navigationBars() | systemBars();
// show two at a time and hide one by one.
mController.show(types);
- mController.hide(Type.navigationBars());
+ mController.hide(navigationBars());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
- mController.hide(Type.systemBars());
+ mController.hide(systemBars());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
@@ -448,7 +451,7 @@ public class InsetsControllerTest {
mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mController.hide(Type.statusBars());
+ mController.hide(statusBars());
mController.cancelExistingAnimations();
assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
@@ -689,7 +692,7 @@ public class InsetsControllerTest {
public void testResizeAnimation_insetsTypes() {
for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
final @AnimationType int expectedAnimationType =
- (InsetsState.toPublicType(type) & Type.systemBars()) != 0
+ (InsetsState.toPublicType(type) & systemBars()) != 0
? ANIMATION_TYPE_RESIZE
: ANIMATION_TYPE_NONE;
doTestResizeAnimation_insetsTypes(type, expectedAnimationType);
@@ -824,15 +827,13 @@ public class InsetsControllerTest {
@Test
public void testRequestedState() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- final InsetsVisibilities request = mTestHost.getRequestedVisibilities();
-
mController.hide(statusBars() | navigationBars());
- assertFalse(request.getVisibility(ITYPE_STATUS_BAR));
- assertFalse(request.getVisibility(ITYPE_NAVIGATION_BAR));
+ assertFalse(mTestHost.isRequestedVisible(statusBars()));
+ assertFalse(mTestHost.isRequestedVisible(navigationBars()));
mController.show(statusBars() | navigationBars());
- assertTrue(request.getVisibility(ITYPE_STATUS_BAR));
- assertTrue(request.getVisibility(ITYPE_NAVIGATION_BAR));
+ assertTrue(mTestHost.isRequestedVisible(statusBars()));
+ assertTrue(mTestHost.isRequestedVisible(navigationBars()));
});
}
@@ -981,20 +982,20 @@ public class InsetsControllerTest {
public static class TestHost extends ViewRootInsetsControllerHost {
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = defaultVisible();
TestHost(ViewRootImpl viewRoot) {
super(viewRoot);
}
@Override
- public void updateRequestedVisibilities(InsetsVisibilities visibilities) {
- mRequestedVisibilities.set(visibilities);
- super.updateRequestedVisibilities(visibilities);
+ public void updateRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ super.updateRequestedVisibleTypes(requestedVisibleTypes);
}
- public InsetsVisibilities getRequestedVisibilities() {
- return mRequestedVisibilities;
+ public boolean isRequestedVisible(@InsetsType int types) {
+ return (mRequestedVisibleTypes & types) != 0;
}
}
}
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
index 5ff357a88483..297b07fac340 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
@@ -20,8 +20,10 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -33,6 +35,7 @@ import org.junit.runner.RunWith;
import java.util.Locale;
import java.util.Objects;
+import java.util.function.Supplier;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -125,6 +128,64 @@ public class InputMethodSubtypeTest {
assertEquals("he", clonedSubtypeHe.getLocale());
}
+ @Test
+ public void testCanonicalizedLanguageTagObjectCache() {
+ final InputMethodSubtype subtype = createSubtypeUsingLanguageTag("en-US");
+ // Verify that the returned object is cached and any subsequent call should return the same
+ // object, which is strictly guaranteed if the method gets called only on a single thread.
+ assertSame(subtype.getCanonicalizedLanguageTag(), subtype.getCanonicalizedLanguageTag());
+ }
+
+ @Test
+ public void testCanonicalizedLanguageTag() {
+ verifyCanonicalizedLanguageTag("en", "en");
+ verifyCanonicalizedLanguageTag("en-US", "en-US");
+ verifyCanonicalizedLanguageTag("en-Latn-US-t-k0-qwerty", "en-Latn-US-t-k0-qwerty");
+
+ verifyCanonicalizedLanguageTag("en-us", "en-US");
+ verifyCanonicalizedLanguageTag("EN-us", "en-US");
+
+ verifyCanonicalizedLanguageTag(null, "");
+ verifyCanonicalizedLanguageTag("", "");
+
+ verifyCanonicalizedLanguageTag("und", "und");
+ verifyCanonicalizedLanguageTag("apparently invalid language tag!!!", "und");
+ }
+
+ private void verifyCanonicalizedLanguageTag(
+ @Nullable String languageTag, @Nullable String expectedLanguageTag) {
+ final InputMethodSubtype subtype = createSubtypeUsingLanguageTag(languageTag);
+ assertEquals(subtype.getCanonicalizedLanguageTag(), expectedLanguageTag);
+ }
+
+ @Test
+ public void testIsSuitableForPhysicalKeyboardLayoutMapping() {
+ final Supplier<InputMethodSubtypeBuilder> getValidBuilder = () ->
+ new InputMethodSubtypeBuilder()
+ .setLanguageTag("en-US")
+ .setIsAuxiliary(false)
+ .setSubtypeMode("keyboard")
+ .setSubtypeId(1);
+
+ assertTrue(getValidBuilder.get().build().isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // mode == "voice" is not suitable.
+ assertFalse(getValidBuilder.get().setSubtypeMode("voice").build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // Auxiliary subtype not suitable.
+ assertFalse(getValidBuilder.get().setIsAuxiliary(true).build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // languageTag == null is not suitable.
+ assertFalse(getValidBuilder.get().setLanguageTag(null).build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+
+ // languageTag == "und" is not suitable.
+ assertFalse(getValidBuilder.get().setLanguageTag("und").build()
+ .isSuitableForPhysicalKeyboardLayoutMapping());
+ }
+
private static InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) {
Parcel parcel = null;
try {
diff --git a/core/tests/coretests/src/com/android/internal/util/FastDataTest.java b/core/tests/coretests/src/com/android/internal/util/FastDataTest.java
index 512ece38a229..de325ab7d186 100644
--- a/core/tests/coretests/src/com/android/internal/util/FastDataTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/FastDataTest.java
@@ -23,6 +23,9 @@ import static org.junit.Assert.fail;
import android.annotation.NonNull;
import android.util.ExceptionUtils;
+import com.android.modules.utils.FastDataInput;
+import com.android.modules.utils.FastDataOutput;
+
import libcore.util.HexEncoding;
import org.junit.Assume;
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 ef5ea563de12..a7d47ef81687 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -161,7 +161,7 @@ class TaskFragmentAnimationSpec {
// The position should be 0-based as we will post translate in
// TaskFragmentAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 30c3d50ed8ad..df5f921f3a62 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -23,6 +23,10 @@
TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
<bool name="config_registerShellTaskOrganizerOnInit">true</bool>
+ <!-- Determines whether to register the shell transitions on init.
+ TODO(b/238217847): This config is temporary until we refactor the base WMComponent. -->
+ <bool name="config_registerShellTransitionsOnInit">true</bool>
+
<!-- Animation duration for PIP when entering. -->
<integer name="config_pipEnterAnimationDuration">425</integer>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 48c5f64e0dd4..bd2ea9c1f822 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -41,7 +41,6 @@ import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -122,7 +121,7 @@ public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
/** Until all users are converted, we may have mixed-use (eg. Car). */
private boolean isUsingShellTransitions() {
- return mTaskViewTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS;
+ return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
index 83335ac24799..07d501201105 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskViewTransitions.java
@@ -87,6 +87,10 @@ public class TaskViewTransitions implements Transitions.TransitionHandler {
// Note: Don't unregister handler since this is a singleton with lifetime bound to Shell
}
+ boolean isEnabled() {
+ return mTransitions.isRegistered();
+ }
+
/**
* Looks through the pending transitions for one matching `taskView`.
* @param taskView the pending transition should be for this.
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 490975cce956..921861ae0913 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
@@ -303,6 +303,7 @@ class ActivityEmbeddingAnimationRunner {
// 3. Animate the TaskFragment using Activity Change info (start/end bounds).
// This is because the TaskFragment surface/change won't contain the Activity's before its
// reparent.
+ Animation changeAnimation = null;
for (TransitionInfo.Change change : info.getChanges()) {
if (change.getMode() != TRANSIT_CHANGE
|| change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
@@ -325,8 +326,14 @@ class ActivityEmbeddingAnimationRunner {
}
}
+ // There are two animations in the array. The first one is for the start leash
+ // (snapshot), and the second one is for the end leash (TaskFragment).
final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
boundsAnimationChange.getEndAbsBounds());
+ // Keep track as we might need to add background color for the animation.
+ // Although there may be multiple change animation, record one of them is sufficient
+ // because the background color will be added to the root leash for the whole animation.
+ changeAnimation = animations[1];
// Create a screenshot based on change, but attach it to the top of the
// boundsAnimationChange.
@@ -345,6 +352,9 @@ class ActivityEmbeddingAnimationRunner {
animations[1], boundsAnimationChange));
}
+ // If there is no corresponding open/close window with the change, we should show background
+ // color to cover the empty part of the screen.
+ boolean shouldShouldBackgroundColor = true;
// Handle the other windows that don't have bounds change in the same transition.
for (TransitionInfo.Change change : info.getChanges()) {
if (handledChanges.contains(change)) {
@@ -359,11 +369,20 @@ class ActivityEmbeddingAnimationRunner {
animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
} else if (Transitions.isClosingType(change.getMode())) {
animation = mAnimationSpec.createChangeBoundsCloseAnimation(change);
+ shouldShouldBackgroundColor = false;
} else {
animation = mAnimationSpec.createChangeBoundsOpenAnimation(change);
+ shouldShouldBackgroundColor = false;
}
adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change));
}
+
+ if (shouldShouldBackgroundColor && changeAnimation != null) {
+ // Change animation may leave part of the screen empty. Show background color to cover
+ // that.
+ changeAnimation.setShowBackdrop(true);
+ }
+
return adapters;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index 58b23667dc18..2bb73692b457 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -158,7 +158,7 @@ class ActivityEmbeddingAnimationSpec {
// The position should be 0-based as we will post translate in
// ActivityEmbeddingAnimationAdapter#onAnimationUpdate
final Animation endTranslate = new TranslateAnimation(startBounds.left - endBounds.left, 0,
- 0, 0);
+ startBounds.top - endBounds.top, 0);
endTranslate.setDuration(CHANGE_ANIMATION_DURATION);
endSet.addAnimation(endTranslate);
// The end leash is resizing, we should update the window crop based on the clip rect.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 725b20525bf7..3972b592c448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -671,10 +671,18 @@ public class BubbleController implements ConfigurationChangeListener {
return;
}
+ mAddedToWindowManager = false;
+ // Put on background for this binder call, was causing jank
+ mBackgroundExecutor.execute(() -> {
+ try {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ } catch (IllegalArgumentException e) {
+ // Not sure if this happens in production, but was happening in tests
+ // (b/253647225)
+ e.printStackTrace();
+ }
+ });
try {
- mAddedToWindowManager = false;
- // Put on background for this binder call, was causing jank
- mBackgroundExecutor.execute(() -> mContext.unregisterReceiver(mBroadcastReceiver));
if (mStackView != null) {
mWindowManager.removeView(mStackView);
mBubbleData.getOverflow().cleanUpExpandedState();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 266cf294a950..bb7c4134aaaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -21,12 +21,10 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.ComponentName;
-import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Slog;
import android.util.SparseArray;
import android.view.IDisplayWindowInsetsController;
@@ -34,16 +32,16 @@ import android.view.IWindowManager;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManagerGlobal;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.view.IInputMethodManager;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -209,7 +207,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
public class PerDisplay implements DisplayInsetsController.OnInsetsChangedListener {
final int mDisplayId;
final InsetsState mInsetsState = new InsetsState();
- final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
InsetsSourceControl mImeSourceControl = null;
int mAnimationDirection = DIRECTION_NONE;
ValueAnimator mAnimation = null;
@@ -332,8 +330,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
@Override
- public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) {
// Do nothing
}
@@ -342,10 +339,12 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
*/
private void setVisibleDirectly(boolean visible) {
mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
- mRequestedVisibilities.setVisibility(InsetsState.ITYPE_IME, visible);
+ mRequestedVisibleTypes = visible
+ ? mRequestedVisibleTypes | WindowInsets.Type.ime()
+ : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
try {
- mWmService.updateDisplayWindowRequestedVisibilities(mDisplayId,
- mRequestedVisibilities);
+ mWmService.updateDisplayWindowRequestedVisibleTypes(mDisplayId,
+ mRequestedVisibleTypes);
} catch (RemoteException e) {
}
}
@@ -514,16 +513,10 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
void removeImeSurface() {
- final IInputMethodManager imms = getImms();
- if (imms != null) {
- try {
- // Remove the IME surface to make the insets invisible for
- // non-client controlled insets.
- imms.removeImeSurface();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to remove IME surface.", e);
- }
- }
+ // Remove the IME surface to make the insets invisible for
+ // non-client controlled insets.
+ InputMethodManagerGlobal.removeImeSurface(
+ e -> Slog.e(TAG, "Failed to remove IME surface.", e));
}
/**
@@ -597,11 +590,6 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
}
- public IInputMethodManager getImms() {
- return IInputMethodManager.Stub.asInterface(
- ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
- }
-
private static boolean haveSameLeash(InsetsSourceControl a, InsetsSourceControl b) {
if (a == b) {
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index 90a01f8c5295..8d4a09d8f371 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -24,7 +24,7 @@ import android.view.IDisplayWindowInsetsController;
import android.view.IWindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
import androidx.annotation.BinderThread;
@@ -177,13 +177,13 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
}
private void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ @InsetsType int requestedVisibleTypes) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
return;
}
for (OnInsetsChangedListener listener : listeners) {
- listener.topFocusedWindowChanged(component, requestedVisibilities);
+ listener.topFocusedWindowChanged(component, requestedVisibleTypes);
}
}
@@ -192,9 +192,9 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
extends IDisplayWindowInsetsController.Stub {
@Override
public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) throws RemoteException {
+ @InsetsType int requestedVisibleTypes) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.topFocusedWindowChanged(component, requestedVisibilities);
+ PerDisplay.this.topFocusedWindowChanged(component, requestedVisibleTypes);
});
}
@@ -239,11 +239,13 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
/**
* Called when top focused window changes to determine whether or not to take over insets
* control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
+ *
* @param component The application component that is open in the top focussed window.
- * @param requestedVisibilities The insets visibilities requested by the focussed window.
+ * @param requestedVisibleTypes The {@link InsetsType} requested visible by the focused
+ * window.
*/
default void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {}
+ @InsetsType int requestedVisibleTypes) {}
/**
* Called when the window insets configuration has changed.
@@ -259,17 +261,17 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
/**
* Called when a set of insets source window should be shown by policy.
*
- * @param types internal insets types (WindowInsets.Type.InsetsType) to show
+ * @param types {@link InsetsType} to show
* @param fromIme true if this request originated from IME (InputMethodService).
*/
- default void showInsets(int types, boolean fromIme) {}
+ default void showInsets(@InsetsType int types, boolean fromIme) {}
/**
* Called when a set of insets source window should be hidden by policy.
*
- * @param types internal insets types (WindowInsets.Type.InsetsType) to hide
+ * @param types {@link InsetsType} to hide
* @param fromIme true if this request originated from IME (InputMethodService).
*/
- default void hideInsets(int types, boolean fromIme) {}
+ default void hideInsets(@InsetsType int types, boolean fromIme) {}
}
-} \ No newline at end of file
+}
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 64dbfbbb738d..8b8e192cf964 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
@@ -507,6 +507,10 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor) {
+ if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
+ // TODO(b/238217847): Force override shell init if registration is disabled
+ shellInit = new ShellInit(mainExecutor);
+ }
return new Transitions(context, shellInit, shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor);
}
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 195ff502e7dc..2fafe67664f8 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
@@ -48,7 +48,6 @@ public class DesktopModeStatus {
try {
int result = Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
return result != 0;
} catch (Exception e) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
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 c91d54a62ae6..b7749fc4c3d4 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
@@ -69,22 +69,28 @@ class DesktopModeTaskRepository {
/**
* Mark a task with given [taskId] as active.
+ *
+ * @return `true` if the task was not active
*/
- fun addActiveTask(taskId: Int) {
+ fun addActiveTask(taskId: Int): Boolean {
val added = activeTasks.add(taskId)
if (added) {
activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return added
}
/**
* Remove task with given [taskId] from active tasks.
+ *
+ * @return `true` if the task was active
*/
- fun removeActiveTask(taskId: Int) {
+ fun removeActiveTask(taskId: Int): Boolean {
val removed = activeTasks.remove(taskId)
if (removed) {
activeTasksListeners.onEach { it.onActiveTasksChanged() }
}
+ return removed
}
/**
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 eaa7158abbe5..90b35a5a55e1 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
@@ -87,11 +87,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
}
if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, true));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
+ });
}
}
@@ -102,11 +104,13 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
mTasks.remove(taskInfo.taskId);
if (DesktopModeStatus.IS_SUPPORTED) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Removing active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, false));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ if (repository.removeActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Removing active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, false);
+ });
}
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -123,13 +127,15 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
if (DesktopModeStatus.IS_SUPPORTED) {
- if (taskInfo.isVisible) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
- mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
- }
- mDesktopModeTaskRepository.ifPresent(
- it -> it.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible));
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ 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, taskInfo.isVisible);
+ });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index b9746e338ced..cbed4b5a501f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -115,8 +115,8 @@ public class PipSurfaceTransactionHelper {
// coordinates so offset the bounds to 0,0
mTmpDestinationRect.offsetTo(0, 0);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset source
- // rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
@@ -129,9 +129,8 @@ public class PipSurfaceTransactionHelper {
: (float) destinationBounds.height() / sourceBounds.height();
scale = (1 - fraction) * startScale + fraction * endScale;
} else {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
}
final float left = destinationBounds.left - insets.left * scale;
final float top = destinationBounds.top - insets.top * scale;
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 d7ca791e3863..21a13103616c 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
@@ -108,6 +108,14 @@ class SplitScreenTransitions {
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
@NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
+ final TransitSession pendingTransition = getPendingTransition(transition);
+ if (pendingTransition != null && pendingTransition.mCanceled) {
+ // The pending transition was canceled, so skip playing animation.
+ t.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ return;
+ }
+
// Play some place-holder fade animations
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -170,9 +178,7 @@ class SplitScreenTransitions {
}
boolean isPendingTransition(IBinder transition) {
- return isPendingEnter(transition)
- || isPendingDismiss(transition)
- || isPendingRecent(transition);
+ return getPendingTransition(transition) != null;
}
boolean isPendingEnter(IBinder transition) {
@@ -187,22 +193,38 @@ class SplitScreenTransitions {
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
+ @Nullable
+ private TransitSession getPendingTransition(IBinder transition) {
+ if (isPendingEnter(transition)) {
+ return mPendingEnter;
+ } else if (isPendingRecent(transition)) {
+ return mPendingRecent;
+ } else if (isPendingDismiss(transition)) {
+ return mPendingDismiss;
+ }
+
+ return null;
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@WindowManager.TransitionType int transitType,
WindowContainerTransaction wct,
@Nullable RemoteTransition remoteTransition,
Transitions.TransitionHandler handler,
- @Nullable TransitionCallback callback) {
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- setEnterTransition(transition, remoteTransition, callback);
+ setEnterTransition(transition, remoteTransition, consumedCallback, finishedCallback);
return transition;
}
/** Sets a transition to enter split. */
void setEnterTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingEnter = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
+ mPendingEnter = new TransitSession(transition, consumedCallback, finishedCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -237,8 +259,9 @@ class SplitScreenTransitions {
}
void setRecentTransition(@NonNull IBinder transition,
- @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
- mPendingRecent = new TransitSession(transition, callback);
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingRecent = new TransitSession(transition, null /* consumedCb */, finishCallback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -248,7 +271,7 @@ class SplitScreenTransitions {
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Enter recent panel");
+ + " deduced Enter recent panel");
}
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
@@ -256,14 +279,9 @@ class SplitScreenTransitions {
if (mergeTarget != mAnimatingTransition) return;
if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
- mPendingRecent.mCallback = new TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Since there's an entering transition merged, recent transition no longer
- // need to handle entering split screen after the transition finished.
- }
- };
+ // Since there's an entering transition merged, recent transition no longer
+ // need to handle entering split screen after the transition finished.
+ mPendingRecent.setFinishedCallback(null);
}
if (mActiveRemoteHandler != null) {
@@ -277,7 +295,7 @@ class SplitScreenTransitions {
}
boolean end() {
- // If its remote, there's nothing we can do right now.
+ // If It's remote, there's nothing we can do right now.
if (mActiveRemoteHandler != null) return false;
for (int i = mAnimations.size() - 1; i >= 0; --i) {
final Animator anim = mAnimations.get(i);
@@ -290,20 +308,20 @@ class SplitScreenTransitions {
@Nullable SurfaceControl.Transaction finishT) {
if (isPendingEnter(transition)) {
if (!aborted) {
- // An enter transition got merged, appends the rest operations to finish entering
+ // An entering transition got merged, appends the rest operations to finish entering
// split screen.
mStageCoordinator.finishEnterSplitScreen(finishT);
mPendingRemoteHandler = null;
}
- mPendingEnter.mCallback.onTransitionConsumed(aborted);
+ mPendingEnter.onConsumed(aborted);
mPendingEnter = null;
mPendingRemoteHandler = null;
} else if (isPendingDismiss(transition)) {
- mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+ mPendingDismiss.onConsumed(aborted);
mPendingDismiss = null;
} else if (isPendingRecent(transition)) {
- mPendingRecent.mCallback.onTransitionConsumed(aborted);
+ mPendingRecent.onConsumed(aborted);
mPendingRecent = null;
mPendingRemoteHandler = null;
}
@@ -312,23 +330,16 @@ class SplitScreenTransitions {
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
- TransitionCallback callback = null;
+ if (wct == null) wct = new WindowContainerTransaction();
if (isPendingEnter(mAnimatingTransition)) {
- callback = mPendingEnter.mCallback;
+ mPendingEnter.onFinished(wct, mFinishTransaction);
mPendingEnter = null;
- }
- if (isPendingDismiss(mAnimatingTransition)) {
- callback = mPendingDismiss.mCallback;
- mPendingDismiss = null;
- }
- if (isPendingRecent(mAnimatingTransition)) {
- callback = mPendingRecent.mCallback;
+ } else if (isPendingRecent(mAnimatingTransition)) {
+ mPendingRecent.onFinished(wct, mFinishTransaction);
mPendingRecent = null;
- }
-
- if (callback != null) {
- if (wct == null) wct = new WindowContainerTransaction();
- callback.onTransitionFinished(wct, mFinishTransaction);
+ } else if (isPendingDismiss(mAnimatingTransition)) {
+ mPendingDismiss.onFinished(wct, mFinishTransaction);
+ mPendingDismiss = null;
}
mPendingRemoteHandler = null;
@@ -363,10 +374,7 @@ class SplitScreenTransitions {
onFinish(null /* wct */, null /* wctCB */);
});
};
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
+ va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
finisher.run();
@@ -376,9 +384,6 @@ class SplitScreenTransitions {
public void onAnimationCancel(Animator animation) {
finisher.run();
}
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
});
mAnimations.add(va);
mTransitions.getAnimExecutor().execute(va::start);
@@ -432,24 +437,66 @@ class SplitScreenTransitions {
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
- /** Clean-up callbacks for transition. */
- interface TransitionCallback {
- /** Calls when the transition got consumed. */
- default void onTransitionConsumed(boolean aborted) {}
+ /** Calls when the transition got consumed. */
+ interface TransitionConsumedCallback {
+ void onConsumed(boolean aborted);
+ }
- /** Calls when the transition finished. */
- default void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {}
+ /** Calls when the transition finished. */
+ interface TransitionFinishedCallback {
+ void onFinished(WindowContainerTransaction wct, SurfaceControl.Transaction t);
}
/** Session for a transition and its clean-up callback. */
static class TransitSession {
final IBinder mTransition;
- TransitionCallback mCallback;
+ TransitionConsumedCallback mConsumedCallback;
+ TransitionFinishedCallback mFinishedCallback;
- TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+ /** Whether the transition was canceled. */
+ boolean mCanceled;
+
+ TransitSession(IBinder transition,
+ @Nullable TransitionConsumedCallback consumedCallback,
+ @Nullable TransitionFinishedCallback finishedCallback) {
mTransition = transition;
- mCallback = callback != null ? callback : new TransitionCallback() {};
+ mConsumedCallback = consumedCallback;
+ mFinishedCallback = finishedCallback;
+
+ }
+
+ /** Sets transition consumed callback. */
+ void setConsumedCallback(@Nullable TransitionConsumedCallback callback) {
+ mConsumedCallback = callback;
+ }
+
+ /** Sets transition finished callback. */
+ void setFinishedCallback(@Nullable TransitionFinishedCallback callback) {
+ mFinishedCallback = callback;
+ }
+
+ /**
+ * Cancels the transition. This should be called before playing animation. A canceled
+ * transition will skip playing animation.
+ *
+ * @param finishedCb new finish callback to override.
+ */
+ void cancel(@Nullable TransitionFinishedCallback finishedCb) {
+ mCanceled = true;
+ setFinishedCallback(finishedCb);
+ }
+
+ void onConsumed(boolean aborted) {
+ if (mConsumedCallback != null) {
+ mConsumedCallback.onConsumed(aborted);
+ }
+ }
+
+ void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (mFinishedCallback != null) {
+ mFinishedCallback.onFinished(finishWct, finishT);
+ }
}
}
@@ -459,7 +506,7 @@ class SplitScreenTransitions {
final @SplitScreen.StageType int mDismissTop;
DismissTransition(IBinder transition, int reason, int dismissTop) {
- super(transition, null /* callback */);
+ super(transition, null /* consumedCallback */, null /* finishedCallback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
}
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 e2ac01f7b003..943419bb8ea2 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
@@ -226,33 +226,36 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
};
- private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- // Check if the recent transition is finished by returning to the current split, so we
- // can restore the divider bar.
- for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
- final WindowContainerTransaction.HierarchyOp op =
- finishWct.getHierarchyOps().get(i);
- final IBinder container = op.getContainer();
- if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
- && (mMainStage.containsContainer(container)
- || mSideStage.containsContainer(container))) {
- updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */);
- setDividerVisibility(true, finishT);
- return;
- }
- }
+ private final SplitScreenTransitions.TransitionFinishedCallback
+ mRecentTransitionFinishedCallback =
+ new SplitScreenTransitions.TransitionFinishedCallback() {
+ @Override
+ public void onFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current
+ // split, so we
+ // can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ updateSurfaceBounds(mSplitLayout, finishT,
+ false /* applyResizingOffset */);
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
- // Dismiss the split screen if it's not returning to split.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
- setSplitsVisible(false);
- setDividerVisibility(false, finishT);
- logExit(EXIT_REASON_UNKNOWN);
- }
- };
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
+ }
+ };
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
@@ -389,15 +392,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (ENABLE_SHELL_TRANSITIONS) {
prepareEnterSplitScreen(wct);
mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ null, this, null /* consumedCallback */, (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
} else {
if (!evictWct.isEmpty()) {
wct.merge(evictWct, true /* transfer */);
@@ -434,28 +433,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.sendPendingIntent(intent, fillInIntent, options);
+
+ // If split screen is not activated, we're expecting to open a pair of apps to split.
+ final int transitType = mMainStage.isActive()
+ ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
- new SplitScreenTransitions.TransitionCallback() {
- @Override
- public void onTransitionConsumed(boolean aborted) {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted
- && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
+ mSplitTransitions.startEnterTransition(transitType, wct, null, this,
+ aborted -> {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePositionAnimated(
+ SplitLayout.reversePosition(mSideStagePosition));
}
-
- @Override
- public void onTransitionFinished(WindowContainerTransaction finishWct,
- SurfaceControl.Transaction finishT) {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
+ } /* consumedCallback */,
+ (finishWct, finishT) -> {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
}
- });
+ } /* finishedCallback */);
}
/** Launches an activity into split by legacy transition. */
@@ -564,9 +560,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/**
* Starts with the second task to a split pair in one transition.
*
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
- * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
+ * {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithTask(WindowContainerTransaction wct, int mainTaskId,
@Nullable Bundle mainOptions, float splitRatio,
@@ -592,7 +588,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.startTask(mainTaskId, mainOptions);
mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null);
setEnterInstanceId(instanceId);
}
@@ -639,7 +635,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
/**
- * @param wct transaction to start the first task
+ * @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
@@ -1082,15 +1078,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
switch (exitReason) {
// One of the apps doesn't support MW
case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
- // User has explicitly dragged the divider to dismiss split
+ // User has explicitly dragged the divider to dismiss split
case EXIT_REASON_DRAG_DIVIDER:
- // Either of the split apps have finished
+ // Either of the split apps have finished
case EXIT_REASON_APP_FINISHED:
- // One of the children enters PiP
+ // One of the children enters PiP
case EXIT_REASON_CHILD_TASK_ENTER_PIP:
- // One of the apps occludes lock screen.
+ // One of the apps occludes lock screen.
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
- // User has unlocked the device after folded
+ // User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
return true;
default:
@@ -1839,7 +1835,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
- mRecentTransitionCallback);
+ mRecentTransitionFinishedCallback);
} else if (mSplitTransitions.mPendingRecent == null) {
// If split-task is not controlled by recents animation
// and occluded by the other fullscreen task, dismiss both.
@@ -1853,8 +1849,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
- mSplitTransitions.setEnterTransition(
- transition, request.getRemoteTransition(), null /* callback */);
+ mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+ null /* consumedCallback */, null /* finishedCallback */);
}
}
return out;
@@ -1873,7 +1869,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
final @WindowManager.TransitionType int type = request.getType();
if (isSplitActive() && !isOpeningType(type)
- && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became "
+ "empty during a mixed transition (one not handled by split),"
+ " so make sure split-screen state is cleaned-up. "
@@ -2031,17 +2027,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- // TODO(b/250853925): fallback logic. Probably start a new transition to exit split before
- // applying anything here. Ideally consolidate with transition-merging.
if (info.getType() == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
if (mainChild == null && sideChild == null) {
- throw new IllegalStateException("Launched a task in split, but didn't receive any"
- + " task in transition.");
+ Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+ mSplitTransitions.mPendingEnter.cancel(null /* finishedCb */);
+ return true;
}
} else {
if (mainChild == null || sideChild == null) {
- throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ Log.w(TAG, "Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
+ final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN :
+ (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
+ mSplitTransitions.mPendingEnter.cancel(
+ (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ return true;
}
}
@@ -2305,7 +2305,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
- mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
+ mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
mSplitUnsupportedToast.show();
}
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 7b498e4f54ec..3929e835cdaa 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
@@ -77,6 +77,7 @@ 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;
@@ -223,7 +224,7 @@ public class TaskSnapshotWindow {
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
- topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
+ info.requestedVisibleTypes, clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
final InsetsState tmpInsetsState = new InsetsState();
@@ -233,7 +234,7 @@ public class TaskSnapshotWindow {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
- info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls,
+ info.requestedVisibleTypes, tmpInputChannel, tmpInsetsState, tmpControls,
new Rect(), sizeCompatScale);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
@@ -263,7 +264,7 @@ public class TaskSnapshotWindow {
public TaskSnapshotWindow(SurfaceControl surfaceControl,
TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
- int currentOrientation, int activityType, InsetsState topWindowInsetsState,
+ int currentOrientation, int activityType, @InsetsType int requestedVisibleTypes,
Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
@@ -276,7 +277,7 @@ public class TaskSnapshotWindow {
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
mTaskBounds = taskBounds;
mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
- windowPrivateFlags, appearance, taskDescription, 1f, topWindowInsetsState);
+ windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
mActivityType = activityType;
@@ -569,11 +570,12 @@ public class TaskSnapshotWindow {
private final int mWindowFlags;
private final int mWindowPrivateFlags;
private final float mScale;
- private final InsetsState mInsetsState;
+ private final @InsetsType int mRequestedVisibleTypes;
private final Rect mSystemBarInsets = new Rect();
SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
- TaskDescription taskDescription, float scale, InsetsState insetsState) {
+ TaskDescription taskDescription, float scale,
+ @InsetsType int requestedVisibleTypes) {
mWindowFlags = windowFlags;
mWindowPrivateFlags = windowPrivateFlags;
mScale = scale;
@@ -592,7 +594,7 @@ public class TaskSnapshotWindow {
&& context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
mStatusBarPaint.setColor(mStatusBarColor);
mNavigationBarPaint.setColor(mNavigationBarColor);
- mInsetsState = insetsState;
+ mRequestedVisibleTypes = requestedVisibleTypes;
}
void setInsets(Rect systemBarInsets) {
@@ -603,7 +605,7 @@ public class TaskSnapshotWindow {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
+ mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
return (int) (mSystemBarInsets.top * mScale);
} else {
return 0;
@@ -614,7 +616,7 @@ public class TaskSnapshotWindow {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
+ mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
}
void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9c2c2fa8598a..af79386caf9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -619,12 +619,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint());
- va.addUpdateListener(animation -> {
+ final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
position, cornerRadius, clipRect);
- });
+ };
+ va.addUpdateListener(updateListener);
final Runnable finisher = () -> {
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
@@ -637,20 +638,30 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
});
};
va.addListener(new AnimatorListenerAdapter() {
+ // It is possible for the end/cancel to be called more than once, which may cause
+ // issues if the animating surface has already been released. Track the finished
+ // state here to skip duplicate callbacks. See b/252872225.
private boolean mFinished = false;
@Override
public void onAnimationEnd(Animator animation) {
- if (mFinished) return;
- mFinished = true;
- finisher.run();
+ onFinish();
}
@Override
public void onAnimationCancel(Animator animation) {
+ onFinish();
+ }
+
+ private void onFinish() {
if (mFinished) return;
mFinished = true;
finisher.run();
+ // The update listener can continue to be called after the animation has ended if
+ // end() is called manually again before the finisher removes the animation.
+ // Remove it manually here to prevent animating a released surface.
+ // See b/252872225.
+ va.removeUpdateListener(updateListener);
}
});
animations.add(va);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 394d6f6bf731..63d31cde4715 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -122,6 +122,8 @@ public class Transitions implements RemoteCallable<Transitions> {
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+ private boolean mIsRegistered = false;
+
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
@@ -163,19 +165,18 @@ public class Transitions implements RemoteCallable<Transitions> {
displayController, pool, mainExecutor, mainHandler, animExecutor);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
mShellController = shellController;
- shellInit.addInitCallback(this::onInit, this);
- }
-
- private void onInit() {
- mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
- this::createExternalInterface, this);
-
// The very last handler (0 in the list) should be the default one.
mHandlers.add(mDefaultTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Default");
// Next lowest priority is remote transitions.
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
+ this::createExternalInterface, this);
ContentResolver resolver = mContext.getContentResolver();
mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
@@ -186,13 +187,23 @@ public class Transitions implements RemoteCallable<Transitions> {
new SettingsObserver());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ mIsRegistered = true;
// Register this transition handler with Core
- mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ try {
+ mOrganizer.registerTransitionPlayer(mPlayerImpl);
+ } catch (RuntimeException e) {
+ mIsRegistered = false;
+ throw e;
+ }
// Pre-load the instance.
TransitionMetrics.getInstance();
}
}
+ public boolean isRegistered() {
+ return mIsRegistered;
+ }
+
private float getTransitionAnimationScaleSetting() {
return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 9967e5f47752..a6f19e7d11d3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -39,7 +39,6 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
-import com.android.internal.view.IInputMethodManager;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.sysui.ShellInit;
@@ -56,8 +55,6 @@ public class DisplayImeControllerTest extends ShellTestCase {
@Mock
private SurfaceControl.Transaction mT;
@Mock
- private IInputMethodManager mMock;
- @Mock
private ShellInit mShellInit;
private DisplayImeController.PerDisplay mPerDisplay;
private Executor mExecutor;
@@ -77,10 +74,6 @@ public class DisplayImeControllerTest extends ShellTestCase {
}
}, mExecutor) {
@Override
- public IInputMethodManager getImms() {
- return mMock;
- }
- @Override
void removeImeSurface() { }
}.new PerDisplay(DEFAULT_DISPLAY, ROTATION_0);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 5f5a3c584ee0..39db328ef0e0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -32,7 +32,7 @@ import android.view.IDisplayWindowInsetsController;
import android.view.IWindowManager;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -108,7 +108,7 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
mController.addInsetsChangedListener(SECOND_DISPLAY, secondListener);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).topFocusedWindowChanged(null,
- new InsetsVisibilities());
+ WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null);
mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false);
@@ -128,7 +128,7 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
assertTrue(secondListener.hideInsetsCount == 0);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).topFocusedWindowChanged(null,
- new InsetsVisibilities());
+ WindowInsets.Type.defaultVisible());
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null);
mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false);
@@ -175,8 +175,7 @@ public class DisplayInsetsControllerTest extends ShellTestCase {
int hideInsetsCount = 0;
@Override
- public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ public void topFocusedWindowChanged(ComponentName component, int requestedVisibleTypes) {
topFocusedWindowChangedCount++;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index ea0033ba4bbb..652f9b38c88f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -181,7 +181,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator, null);
+ new RemoteTransition(testRemote), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -421,7 +421,7 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index e5ae2962e6e4..11fda8bf7bbc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -249,7 +249,7 @@ public class StartingSurfaceDrawerTests extends ShellTestCase {
doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay(
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
- any() /* requestedVisibility */, any() /* outInputChannel */,
+ anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */,
any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
index 3de50bb60470..004df2a29e58 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
@@ -39,9 +39,9 @@ import android.graphics.ColorSpace;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
-import android.view.InsetsState;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowInsets;
import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -84,7 +84,8 @@ public class TaskSnapshotWindowTest extends ShellTestCase {
createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
- new InsetsState(), null /* clearWindow */, new TestShellExecutor());
+ WindowInsets.Type.defaultVisible(), null /* clearWindow */,
+ new TestShellExecutor());
}
private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 7a412a0ed2de..b38f9ea39136 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -207,7 +207,7 @@ public final class GnssCapabilities implements Parcelable {
/**
* Returns {@code true} if GNSS chipset supports single shot locating, {@code false} otherwise.
*/
- public boolean hasSingleShot() {
+ public boolean hasSingleShotFix() {
return (mTopFlags & TOP_HAL_CAPABILITY_SINGLE_SHOT) != 0;
}
@@ -482,7 +482,7 @@ public final class GnssCapabilities implements Parcelable {
if (hasMsa()) {
builder.append("MSA ");
}
- if (hasSingleShot()) {
+ if (hasSingleShotFix()) {
builder.append("SINGLE_SHOT ");
}
if (hasOnDemandTime()) {
@@ -602,7 +602,7 @@ public final class GnssCapabilities implements Parcelable {
/**
* Sets single shot locating capability.
*/
- public @NonNull Builder setHasSingleShot(boolean capable) {
+ public @NonNull Builder setHasSingleShotFix(boolean capable) {
mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_SINGLE_SHOT, capable);
return this;
}
diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java
index 23390fce1a5f..09f40e80f885 100644
--- a/location/java/android/location/GnssStatus.java
+++ b/location/java/android/location/GnssStatus.java
@@ -199,15 +199,15 @@ public final class GnssStatus implements Parcelable {
* <li>93-106 as the frequency channel number (FCN) (-7 to +6) plus 100.
* i.e. encode FCN of -7 as 93, 0 as 100, and +6 as 106</li>
* </ul></li>
- * <li>QZSS: 193-200</li>
+ * <li>QZSS: 183-206</li>
* <li>Galileo: 1-36</li>
- * <li>Beidou: 1-37</li>
+ * <li>Beidou: 1-63</li>
* <li>IRNSS: 1-14</li>
* </ul>
*
* @param satelliteIndex An index from zero to {@link #getSatelliteCount()} - 1
*/
- @IntRange(from = 1, to = 200)
+ @IntRange(from = 1, to = 206)
public int getSvid(@IntRange(from = 0) int satelliteIndex) {
return mSvidWithFlags[satelliteIndex] >> SVID_SHIFT_WIDTH;
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
index 24cb5f948474..602e31cbd130 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
@@ -85,7 +85,7 @@ public class SliceStoreActivity extends Activity {
SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
- mWebView = new WebView(getApplicationContext());
+ mWebView = new WebView(this);
setContentView(mWebView);
mWebView.loadUrl(mUrl.toString());
// TODO(b/245882601): Get back response from WebView
diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml
index 428f2dc2eb35..2000d9675ca4 100644
--- a/packages/CompanionDeviceManager/res/values/styles.xml
+++ b/packages/CompanionDeviceManager/res/values/styles.xml
@@ -49,7 +49,6 @@
<style name="DescriptionSummary">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:gravity">center</item>
<item name="android:layout_marginTop">18dp</item>
<item name="android:layout_marginLeft">18dp</item>
<item name="android:layout_marginRight">18dp</item>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 7f843a2458f0..c6779fa39473 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -4,7 +4,8 @@
<string name="string_cancel">Cancel</string>
<string name="string_continue">Continue</string>
<string name="string_more_options">More options</string>
- <string name="string_create_at_another_place">Create at another place</string>
+ <string name="string_create_in_another_place">Create in another place</string>
+ <string name="string_save_to_another_place">Save to another place</string>
<string name="string_no_thanks">No thanks</string>
<string name="passkey_creation_intro_title">A simple way to sign in safely</string>
<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>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 373fb80e87be..8e30208e75d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
@@ -23,6 +22,7 @@ import androidx.compose.material.IconButton
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.ModalBottomSheetValue
import androidx.compose.material.Text
+import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
@@ -458,11 +458,22 @@ fun CreationSelectionCard(
PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
onOptionSelected = onOptionSelected)
}
- if (multiProvider) {
- item {
- MoreOptionsRow(onSelect = onMoreOptionsSelected)
- }
- }
+ }
+ }
+ if (multiProvider) {
+ 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,
+ )
}
}
Divider(
@@ -563,23 +574,4 @@ fun MoreOptionsInfoRow(
)
}
}
-}
-
-@ExperimentalMaterialApi
-@Composable
-fun MoreOptionsRow(onSelect: () -> Unit) {
- Chip(
- modifier = Modifier.fillMaxWidth().height(52.dp),
- onClick = onSelect,
- colors = ChipDefaults.chipColors(
- backgroundColor = Grey100,
- leadingIconContentColor = Grey100
- ),
- shape = Shapes.large
- ) {
- Text(
- text = stringResource(R.string.string_create_at_another_place),
- style = Typography.h6,
- )
- }
-}
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
new file mode 100644
index 000000000000..d4341b498fe0
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.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.credentialmanager.jetpack
+
+import android.app.slice.Slice
+import android.credentials.ui.Entry
+import android.graphics.drawable.Icon
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class ActionUi(
+ val icon: Icon,
+ val text: CharSequence,
+ val subtext: CharSequence?,
+) {
+ companion object {
+ fun fromSlice(slice: Slice): ActionUi {
+ var icon: Icon? = null
+ var text: CharSequence? = null
+ var subtext: CharSequence? = null
+
+ val items = slice.items
+ items.forEach {
+ if (it.hasHint(Entry.HINT_ACTION_ICON)) {
+ icon = it.icon
+ } else if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
+ text = it.text
+ } else if (it.hasHint(Entry.HINT_ACTION_SUBTEXT)) {
+ subtext = it.text
+ }
+ }
+ // TODO: fail NPE more elegantly.
+ return ActionUi(icon!!, text!!, subtext)
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 2820ed7bc96e..3b159e95cc55 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -59,9 +59,9 @@ dependencies {
api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
- api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha02"
+ api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
api "androidx.navigation:navigation-compose:2.5.0"
- api "com.google.android.material:material:1.6.1"
+ 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"
}
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
deleted file mode 100644
index 67dd2b0cc5e0..000000000000
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<resources>
-
- <style name="Theme.SpaLib.DayNight" />
-</resources>
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index e0e5fc211ec6..25846ec2d20b 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -16,12 +16,10 @@
-->
<resources>
- <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
+ <style name="Theme.SpaLib" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
- </style>
-
- <style name="Theme.SpaLib.DayNight">
- <item name="android:windowLightStatusBar">true</item>
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index d3efaa7480f8..c3c90ab4fdb8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -27,6 +27,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -66,8 +67,9 @@ open class BrowseActivity : ComponentActivity() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
+ WindowCompat.setDecorFitsSystemWindows(window, false)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
setContent {
@@ -83,35 +85,19 @@ open class BrowseActivity : ComponentActivity() {
val navController = rememberNavController()
val nullPage = SettingsPage.createNull()
CompositionLocalProvider(navController.localNavController()) {
- NavHost(navController, nullPage.sppName) {
+ NavHost(
+ navController = navController,
+ startDestination = nullPage.sppName,
+ ) {
composable(nullPage.sppName) {}
for (spp in sppRepository.getAllProviders()) {
composable(
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
) { navBackStackEntry ->
- val lifecycleOwner = LocalLifecycleOwner.current
- val sp = remember(navBackStackEntry.arguments) {
+ PageLogger(remember(navBackStackEntry.arguments) {
spp.createSettingsPage(arguments = navBackStackEntry.arguments)
- }
-
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- sp.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- sp.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
- }
+ })
spp.Page(navBackStackEntry.arguments)
}
@@ -122,6 +108,28 @@ open class BrowseActivity : ComponentActivity() {
}
@Composable
+ private fun PageLogger(settingsPage: SettingsPage) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ if (event == Lifecycle.Event.ON_START) {
+ settingsPage.enterPage()
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ settingsPage.leavePage()
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+ }
+
+ @Composable
private fun InitialDestinationNavigator() {
val sppRepository by spaEnvironment.pageProviderRepository
val destinationNavigated = rememberSaveable { mutableStateOf(false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
new file mode 100644
index 000000000000..18335ff6eba5
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.PaddingValues
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+
+internal fun PaddingValues.horizontalValues(): PaddingValues = HorizontalPaddingValues(this)
+
+internal fun PaddingValues.verticalValues(): PaddingValues = VerticalPaddingValues(this)
+
+private class HorizontalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateLeftPadding(layoutDirection)
+
+ override fun calculateTopPadding(): Dp = 0.dp
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) =
+ paddingValues.calculateRightPadding(layoutDirection)
+
+ override fun calculateBottomPadding() = 0.dp
+}
+
+private class VerticalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+ override fun calculateLeftPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateTopPadding(): Dp = paddingValues.calculateTopPadding()
+
+ override fun calculateRightPadding(layoutDirection: LayoutDirection) = 0.dp
+
+ override fun calculateBottomPadding() = paddingValues.calculateBottomPadding()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 9eaa88ae3168..26491d51e838 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -62,7 +62,7 @@ class DebugActivity : ComponentActivity() {
private val spaEnvironment get() = SpaEnvironmentFactory.instance
override fun onCreate(savedInstanceState: Bundle?) {
- setTheme(R.style.Theme_SpaLib_DayNight)
+ setTheme(R.style.Theme_SpaLib)
super.onCreate(savedInstanceState)
spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
index eb20ac5c5f09..711c8a753532 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
@@ -20,6 +20,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
@@ -34,6 +35,7 @@ fun HomeScaffold(title: String, content: @Composable () -> Unit) {
Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
+ .systemBarsPadding()
.verticalScroll(rememberScrollState()),
) {
Text(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
index 9a17b2a8cb78..d17a8dcd0161 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
@@ -19,7 +19,7 @@ package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Scaffold
@@ -27,6 +27,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is scrollable and wrapped in a [Column].
@@ -42,8 +44,9 @@ fun RegularScaffold(
) {
SettingsScaffold(title, actions) { paddingValues ->
Column(Modifier.verticalScroll(rememberScrollState())) {
- Spacer(Modifier.padding(paddingValues))
+ Spacer(Modifier.height(paddingValues.calculateTopPadding()))
content()
+ Spacer(Modifier.height(paddingValues.calculateBottomPadding()))
}
}
}
@@ -52,6 +55,13 @@ fun RegularScaffold(
@Composable
private fun RegularScaffoldPreview() {
SettingsTheme {
- RegularScaffold(title = "Display") {}
+ RegularScaffold(title = "Display") {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
}
}
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 4f83ad6bd291..efc623af9cc0 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
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalMaterial3Api::class)
-
package com.android.settingslib.spa.widget.scaffold
import androidx.activity.compose.BackHandler
import androidx.appcompat.R
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -31,10 +30,13 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@@ -48,45 +50,57 @@ import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.hideKeyboardAction
+import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsOpacity
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is can be full screen, and with a search feature built-in.
*/
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchScaffold(
title: String,
actions: @Composable RowScope.() -> Unit = {},
- content: @Composable (searchQuery: State<String>) -> Unit,
+ content: @Composable (bottomPadding: Dp, searchQuery: State<String>) -> Unit,
) {
val viewModel: SearchScaffoldViewModel = viewModel()
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
SearchableTopAppBar(
title = title,
actions = actions,
+ scrollBehavior = scrollBehavior,
searchQuery = viewModel.searchQuery,
) { viewModel.searchQuery = it }
},
) { paddingValues ->
Box(
Modifier
- .padding(paddingValues)
- .fillMaxSize()
+ .padding(paddingValues.horizontalValues())
+ .padding(top = paddingValues.calculateTopPadding())
+ .fillMaxSize(),
) {
- val searchQuery = remember {
- derivedStateOf { viewModel.searchQuery?.text ?: "" }
- }
- content(searchQuery)
+ content(
+ bottomPadding = paddingValues.calculateBottomPadding(),
+ searchQuery = remember {
+ derivedStateOf { viewModel.searchQuery?.text ?: "" }
+ },
+ )
}
}
}
@@ -95,10 +109,12 @@ internal class SearchScaffoldViewModel : ViewModel() {
var searchQuery: TextFieldValue? by mutableStateOf(null)
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchableTopAppBar(
title: String,
actions: @Composable RowScope.() -> Unit,
+ scrollBehavior: TopAppBarScrollBehavior,
searchQuery: TextFieldValue?,
onSearchQueryChange: (TextFieldValue?) -> Unit,
) {
@@ -110,13 +126,17 @@ private fun SearchableTopAppBar(
actions = actions,
)
} else {
- SettingsTopAppBar(title) {
- SearchAction { onSearchQueryChange(TextFieldValue()) }
+ SettingsTopAppBar(title, scrollBehavior) {
+ SearchAction {
+ scrollBehavior.collapse()
+ onSearchQueryChange(TextFieldValue())
+ }
actions()
}
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchTopAppBar(
query: TextFieldValue,
@@ -124,21 +144,24 @@ private fun SearchTopAppBar(
onClose: () -> Unit,
actions: @Composable RowScope.() -> Unit = {},
) {
- TopAppBar(
- title = { SearchBox(query, onQueryChange) },
- modifier = Modifier.statusBarsPadding(),
- navigationIcon = { CollapseAction(onClose) },
- actions = {
- if (query.text.isNotEmpty()) {
- ClearAction { onQueryChange(TextFieldValue()) }
- }
- actions()
- },
- colors = settingsTopAppBarColors(),
- )
+ Surface(color = SettingsTheme.colorScheme.surfaceHeader) {
+ TopAppBar(
+ title = { SearchBox(query, onQueryChange) },
+ modifier = Modifier.statusBarsPadding(),
+ navigationIcon = { CollapseAction(onClose) },
+ actions = {
+ if (query.text.isNotEmpty()) {
+ ClearAction { onQueryChange(TextFieldValue()) }
+ }
+ actions()
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
+ )
+ }
BackHandler { onClose() }
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
val focusRequester = remember { FocusRequester() }
@@ -184,6 +207,15 @@ private fun SearchTopAppBarPreview() {
@Composable
private fun SearchScaffoldPreview() {
SettingsTheme {
- SearchScaffold(title = "App notifications") {}
+ SearchScaffold(title = "App notifications") { _, _ ->
+ Column {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 3bc3dd72d353..f4e504a954a2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -16,13 +16,23 @@
package com.android.settingslib.spa.widget.scaffold
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.compose.verticalValues
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
/**
* A [Scaffold] which content is can be full screen when needed.
@@ -34,16 +44,30 @@ fun SettingsScaffold(
actions: @Composable RowScope.() -> Unit = {},
content: @Composable (PaddingValues) -> Unit,
) {
+ val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
- topBar = { SettingsTopAppBar(title, actions) },
- content = content,
- )
+ modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+ topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
+ ) { paddingValues ->
+ Box(Modifier.padding(paddingValues.horizontalValues())) {
+ content(paddingValues.verticalValues())
+ }
+ }
}
@Preview
@Composable
private fun SettingsScaffoldPreview() {
SettingsTheme {
- SettingsScaffold(title = "Display") {}
+ SettingsScaffold(title = "Display") { paddingValues ->
+ Column(Modifier.padding(paddingValues)) {
+ Preference(object : PreferenceModel {
+ override val title = "Item 1"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Item 2"
+ })
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
index 93535203b1b9..f7cb035cbf93 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
@@ -17,41 +17,70 @@
package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LargeTopAppBar
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
+import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.rememberSettingsTypography
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun SettingsTopAppBar(
title: String,
+ scrollBehavior: TopAppBarScrollBehavior,
actions: @Composable RowScope.() -> Unit,
) {
- TopAppBar(
- title = {
- Text(
- text = title,
- modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
- overflow = TextOverflow.Ellipsis,
- maxLines = 1,
- )
- },
- navigationIcon = { NavigateBack() },
- actions = actions,
- colors = settingsTopAppBarColors(),
+ val colorScheme = MaterialTheme.colorScheme
+ // TODO: Remove MaterialTheme() after top app bar color fixed in AndroidX.
+ MaterialTheme(
+ colorScheme = remember { colorScheme.copy(surface = colorScheme.background) },
+ typography = rememberSettingsTypography(),
+ ) {
+ LargeTopAppBar(
+ title = { Title(title) },
+ navigationIcon = { NavigateBack() },
+ actions = actions,
+ colors = largeTopAppBarColors(),
+ scrollBehavior = scrollBehavior,
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+internal fun TopAppBarScrollBehavior.collapse() {
+ with(state) {
+ heightOffset = heightOffsetLimit
+ }
+}
+
+@Composable
+private fun Title(title: String) {
+ Text(
+ text = title,
+ modifier = Modifier
+ .padding(WindowInsets.navigationBars.asPaddingValues().horizontalValues())
+ .padding(SettingsDimension.itemPaddingAround),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-internal fun settingsTopAppBarColors() = TopAppBarDefaults.smallTopAppBarColors(
- containerColor = SettingsTheme.colorScheme.surfaceHeader,
+private fun largeTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background,
scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
index 652e54de5e39..d09aec9460dd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
@@ -16,21 +16,42 @@
package com.android.settingslib.spa.widget.util
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.repeatable
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
+import com.android.settingslib.spa.framework.theme.SettingsTheme
@Composable
internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
val entryData = LocalEntryDataProvider.current
- val isHighlighted = rememberSaveable { entryData.isHighlighted }
- val backgroundColor =
- if (isHighlighted) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent
+ var isHighlighted by rememberSaveable { mutableStateOf(false) }
+ SideEffect {
+ isHighlighted = entryData.isHighlighted
+ }
+
+ val backgroundColor by animateColorAsState(
+ targetValue = when {
+ isHighlighted -> MaterialTheme.colorScheme.surfaceVariant
+ else -> SettingsTheme.colorScheme.background
+ },
+ animationSpec = repeatable(
+ iterations = 3,
+ animation = tween(durationMillis = 500),
+ repeatMode = RepeatMode.Restart
+ )
+ )
Box(modifier = Modifier.background(color = backgroundColor)) {
UiLayoutFn()
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
new file mode 100644
index 000000000000..1964c436d138
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.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 RegularScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun regularScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun regularScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ RegularScaffold(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/scaffold/SearchScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
index ec3379dd46ee..c3e1d544a6ab 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
@@ -43,7 +43,7 @@ class SearchScaffoldTest {
@Test
fun initialState_titleIsDisplayed() {
composeTestRule.setContent {
- SearchScaffold(title = TITLE) {}
+ SearchScaffold(title = TITLE) { _, _ -> }
}
composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
@@ -116,7 +116,7 @@ class SearchScaffoldTest {
private fun setContent(): State<String> {
lateinit var actualSearchQuery: State<String>
composeTestRule.setContent {
- SearchScaffold(title = TITLE) { searchQuery ->
+ SearchScaffold(title = TITLE) { _, searchQuery ->
SideEffect {
actualSearchQuery = searchQuery
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
index 0c84eac45cb7..0c745d5d5b3d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spa.widget.scaffold
-import androidx.compose.runtime.Composable
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotDisplayed
import androidx.compose.ui.test.assertIsNotSelected
@@ -31,15 +30,13 @@ import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class SettingsPagerKtTest {
+class SettingsPagerTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun twoPage_initialState() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Personal").assertIsSelected()
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
@@ -49,9 +46,7 @@ class SettingsPagerKtTest {
@Test
fun twoPage_afterSwitch() {
- composeTestRule.setContent {
- TestTwoPage()
- }
+ setTwoPagesContent()
composeTestRule.onNodeWithText("Work").performClick()
@@ -73,11 +68,12 @@ class SettingsPagerKtTest {
composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
}
-}
-@Composable
-private fun TestTwoPage() {
- SettingsPager(listOf("Personal", "Work")) {
- SettingsTitle(title = "Page $it")
+ private fun setTwoPagesContent() {
+ composeTestRule.setContent {
+ SettingsPager(listOf("Personal", "Work")) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
new file mode 100644
index 000000000000..f04240485386
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.Text
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun settingsScaffold_titleIsDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_itemsAreDisplayed() {
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ @Test
+ fun settingsScaffold_noHorizontalPadding() {
+ lateinit var actualPaddingValues: PaddingValues
+
+ composeTestRule.setContent {
+ SettingsScaffold(title = TITLE) { paddingValues ->
+ SideEffect {
+ actualPaddingValues = paddingValues
+ }
+ }
+ }
+
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+ assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
index c1ac5d473dba..8954d22f45cc 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
@@ -35,6 +35,9 @@ val ApplicationInfo.userHandle: UserHandle
/** Checks whether a flag is associated with the application. */
fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0
+/** Checks whether the application is currently installed. */
+val ApplicationInfo.installed: Boolean get() = hasFlag(ApplicationInfo.FLAG_INSTALLED)
+
/** Checks whether the application is disabled until used. */
val ApplicationInfo.isDisabledUntilUsed: Boolean
get() = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
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 408b9df5e3ef..3cd8378b8960 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
@@ -25,12 +25,12 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.Dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.settingslib.spa.framework.compose.LogCompositions
import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppListConfig
@@ -55,10 +55,11 @@ internal fun <T : AppRecord> AppList(
option: State<Int>,
searchQuery: State<String>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
LogCompositions(TAG, appListConfig.userId.toString())
val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
- AppListWidget(appListData, listModel, appItem)
+ AppListWidget(appListData, listModel, appItem, bottomPadding)
}
@Composable
@@ -66,6 +67,7 @@ private fun <T : AppRecord> AppListWidget(
appListData: State<AppListData<T>?>,
listModel: AppListModel<T>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ bottomPadding: Dp,
) {
val timeMeasurer = rememberTimeMeasurer(TAG)
appListData.value?.let { (list, option) ->
@@ -77,7 +79,7 @@ private fun <T : AppRecord> AppListWidget(
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
- contentPadding = PaddingValues(bottom = SettingsDimension.itemPaddingVertical),
+ contentPadding = PaddingValues(bottom = bottomPadding),
) {
items(count = list.size, key = { option to list[it].record.app.packageName }) {
val appEntry = list[it]
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 99376b0005e4..29533679d9c1 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
@@ -52,7 +52,7 @@ fun <T : AppRecord> AppListPage(
actions = {
ShowSystemAction(showSystem.value) { showSystem.value = it }
},
- ) { searchQuery ->
+ ) { bottomPadding, searchQuery ->
WorkProfilePager(primaryUserOnly) { userInfo ->
Column(Modifier.fillMaxSize()) {
val options = remember { listModel.getSpinnerOptions() }
@@ -68,6 +68,7 @@ fun <T : AppRecord> AppListPage(
option = selectedOption,
searchQuery = searchQuery,
appItem = appItem,
+ bottomPadding = bottomPadding,
)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index eb53ea1d44f7..950ee21ae7b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -758,23 +758,16 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
public boolean isBusy() {
- for (CachedBluetoothDevice memberDevice : getMemberDevice()) {
- if (isBusyState(memberDevice)) {
- return true;
- }
- }
- return isBusyState(this);
- }
-
- private boolean isBusyState(CachedBluetoothDevice device){
- for (LocalBluetoothProfile profile : device.getProfiles()) {
- int status = device.getProfileConnectionState(profile);
- if (status == BluetoothProfile.STATE_CONNECTING
- || status == BluetoothProfile.STATE_DISCONNECTING) {
- return true;
+ synchronized (mProfileLock) {
+ for (LocalBluetoothProfile profile : mProfiles) {
+ int status = getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTING
+ || status == BluetoothProfile.STATE_DISCONNECTING) {
+ return true;
+ }
}
+ return getBondState() == BluetoothDevice.BOND_BONDING;
}
- return device.getBondState() == BluetoothDevice.BOND_BONDING;
}
private boolean updateProfiles() {
@@ -920,7 +913,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
@Override
public String toString() {
- return mDevice.toString();
+ return "CachedBluetoothDevice ("
+ + "anonymizedAddress="
+ + mDevice.getAnonymizedAddress()
+ + ", name="
+ + getName()
+ + ", groupId="
+ + mGroupId
+ + ")";
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 8a9f9dd9c3fb..fb861da0a7f0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -231,7 +231,7 @@ public class LocalBluetoothProfileManager {
if (DEBUG) {
Log.d(TAG, "Adding local Volume Control profile");
}
- mVolumeControlProfile = new VolumeControlProfile();
+ mVolumeControlProfile = new VolumeControlProfile(mContext, mDeviceManager, this);
// Note: no event handler for VCP, only for being connectable.
mProfileNameMap.put(VolumeControlProfile.NAME, mVolumeControlProfile);
}
@@ -553,6 +553,10 @@ public class LocalBluetoothProfileManager {
return mCsipSetCoordinatorProfile;
}
+ public VolumeControlProfile getVolumeControlProfile() {
+ return mVolumeControlProfile;
+ }
+
/**
* Fill in a list of LocalBluetoothProfile objects that are supported by
* the local device and the remote device.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 511df282ea4b..57867be53bb9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -16,18 +16,91 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.RequiresApi;
/**
* VolumeControlProfile handles Bluetooth Volume Control Controller role
*/
public class VolumeControlProfile implements LocalBluetoothProfile {
private static final String TAG = "VolumeControlProfile";
+ private static boolean DEBUG = true;
static final String NAME = "VCP";
// Order of this profile in device profiles list
- private static final int ORDINAL = 23;
+ private static final int ORDINAL = 1;
+
+ private Context mContext;
+ private final CachedBluetoothDeviceManager mDeviceManager;
+ private final LocalBluetoothProfileManager mProfileManager;
+
+ private BluetoothVolumeControl mService;
+ private boolean mIsProfileReady;
+
+ // These callbacks run on the main thread.
+ private final class VolumeControlProfileServiceListener
+ implements BluetoothProfile.ServiceListener {
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service connected");
+ }
+ mService = (BluetoothVolumeControl) proxy;
+ // We just bound to the service, so refresh the UI for any connected
+ // VolumeControlProfile devices.
+ List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+ while (!deviceList.isEmpty()) {
+ BluetoothDevice nextDevice = deviceList.remove(0);
+ CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+ // we may add a new device here, but generally this should not happen
+ if (device == null) {
+ if (DEBUG) {
+ Log.d(TAG, "VolumeControlProfile found new device: " + nextDevice);
+ }
+ device = mDeviceManager.addDevice(nextDevice);
+ }
+ device.onProfileStateChanged(VolumeControlProfile.this,
+ BluetoothProfile.STATE_CONNECTED);
+ device.refresh();
+ }
+
+ mProfileManager.callServiceConnectedListeners();
+ mIsProfileReady = true;
+ }
+
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) {
+ Log.d(TAG, "Bluetooth service disconnected");
+ }
+ mProfileManager.callServiceDisconnectedListeners();
+ mIsProfileReady = false;
+ }
+ }
+
+ VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ LocalBluetoothProfileManager profileManager) {
+ mContext = context;
+ mDeviceManager = deviceManager;
+ mProfileManager = profileManager;
+
+ BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
+ new VolumeControlProfile.VolumeControlProfileServiceListener(),
+ BluetoothProfile.VOLUME_CONTROL);
+ }
@Override
public boolean accessProfileEnabled() {
@@ -39,29 +112,70 @@ public class VolumeControlProfile implements LocalBluetoothProfile {
return true;
}
+ /**
+ * Get VolumeControlProfile devices matching connection states{
+ *
+ * @return Matching device list
+ * @code BluetoothProfile.STATE_CONNECTED,
+ * @code BluetoothProfile.STATE_CONNECTING,
+ * @code BluetoothProfile.STATE_DISCONNECTING}
+ */
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(0);
+ }
+ return mService.getDevicesMatchingConnectionStates(
+ new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
@Override
public int getConnectionStatus(BluetoothDevice device) {
- return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle VCP
+ if (mService == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ return mService.getConnectionState(device);
}
@Override
public boolean isEnabled(BluetoothDevice device) {
- return false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
@Override
public int getConnectionPolicy(BluetoothDevice device) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle VCP
+ if (mService == null || device == null) {
+ return CONNECTION_POLICY_FORBIDDEN;
+ }
+ return mService.getConnectionPolicy(device);
}
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- return false;
+ boolean isSuccessful = false;
+ if (mService == null || device == null) {
+ return false;
+ }
+ if (DEBUG) {
+ Log.d(TAG, device.getAnonymizedAddress() + " setEnabled: " + enabled);
+ }
+ if (enabled) {
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ }
+ } else {
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ }
+
+ return isSuccessful;
}
@Override
public boolean isProfileReady() {
- return true;
+ return mIsProfileReady;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index 1606540da3fd..2614644feb20 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -92,7 +92,8 @@ public class DreamBackend {
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
COMPLICATION_TYPE_HOME_CONTROLS,
- COMPLICATION_TYPE_SMARTSPACE
+ COMPLICATION_TYPE_SMARTSPACE,
+ COMPLICATION_TYPE_MEDIA_ENTRY
})
@Retention(RetentionPolicy.SOURCE)
public @interface ComplicationType {
@@ -105,6 +106,7 @@ public class DreamBackend {
public static final int COMPLICATION_TYPE_CAST_INFO = 5;
public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
+ public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8;
private final Context mContext;
private final IDreamManager mDreamManager;
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 03d9f2db01f2..30d382023b5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -357,5 +357,12 @@ public class DataServiceUtils {
* {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription";
+
+ /**
+ * The name of the active data subscription state column, see
+ * {@link SubscriptionManager#getActiveDataSubscriptionId()}.
+ */
+ public static final String COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION =
+ "isActiveDataSubscriptionId";
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 329bd9bfb9e7..23566f760444 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -42,7 +42,7 @@ public class SubscriptionInfoEntity {
boolean isUsableSubscription, boolean isActiveSubscriptionId,
boolean isAvailableSubscription, boolean isDefaultVoiceSubscription,
boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription,
- boolean isDefaultSubscription) {
+ boolean isDefaultSubscription, boolean isActiveDataSubscriptionId) {
this.subId = subId;
this.simSlotIndex = simSlotIndex;
this.carrierId = carrierId;
@@ -72,6 +72,7 @@ public class SubscriptionInfoEntity {
this.isDefaultSmsSubscription = isDefaultSmsSubscription;
this.isDefaultDataSubscription = isDefaultDataSubscription;
this.isDefaultSubscription = isDefaultSubscription;
+ this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
}
@PrimaryKey
@@ -165,6 +166,9 @@ public class SubscriptionInfoEntity {
@ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION)
public boolean isDefaultSubscription;
+ @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
+ public boolean isActiveDataSubscriptionId;
+
public int getSubId() {
return Integer.valueOf(subId);
}
@@ -213,6 +217,7 @@ public class SubscriptionInfoEntity {
result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription);
result = 31 * result + Boolean.hashCode(isDefaultDataSubscription);
result = 31 * result + Boolean.hashCode(isDefaultSubscription);
+ result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId);
return result;
}
@@ -254,7 +259,8 @@ public class SubscriptionInfoEntity {
&& isDefaultVoiceSubscription == info.isDefaultVoiceSubscription
&& isDefaultSmsSubscription == info.isDefaultSmsSubscription
&& isDefaultDataSubscription == info.isDefaultDataSubscription
- && isDefaultSubscription == info.isDefaultSubscription;
+ && isDefaultSubscription == info.isDefaultSubscription
+ && isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
}
public String toString() {
@@ -317,6 +323,8 @@ public class SubscriptionInfoEntity {
.append(isDefaultDataSubscription)
.append(", isDefaultSubscription = ")
.append(isDefaultSubscription)
+ .append(", isActiveDataSubscriptionId = ")
+ .append(isActiveDataSubscriptionId)
.append(")}");
return builder.toString();
}
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 315ab0aac878..79e99387b2fa 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
@@ -1069,80 +1069,4 @@ public class CachedBluetoothDeviceTest {
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
}
-
- @Test
- public void isBusy_mainDeviceIsConnecting_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_mainDeviceIsBonding_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_memberDeviceIsConnecting_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_memberDeviceIsBonding_returnsBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isTrue();
- }
-
- @Test
- public void isBusy_allDevicesAreNotBusy_returnsNotBusy() {
- mCachedDevice.addMemberDevice(mSubCachedDevice);
- updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
- when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-
- assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
- assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
- assertThat(mCachedDevice.isBusy()).isFalse();
- }
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ccbfac226c46..fa96a2f0ee7f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -5533,13 +5533,17 @@ public class SettingsProvider extends ContentProvider {
}
if (currentVersion == 210) {
final SettingsState secureSettings = getSecureSettingsLocked(userId);
- final int defaultValueVibrateIconEnabled = getContext().getResources()
- .getInteger(R.integer.def_statusBarVibrateIconEnabled);
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
- String.valueOf(defaultValueVibrateIconEnabled),
- null /* tag */, true /* makeDefault */,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ final Setting currentSetting = secureSettings.getSettingLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON);
+ if (currentSetting.isNull()) {
+ final int defaultValueVibrateIconEnabled = getContext().getResources()
+ .getInteger(R.integer.def_statusBarVibrateIconEnabled);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ String.valueOf(defaultValueVibrateIconEnabled),
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
currentVersion = 211;
}
// vXXX: Add new settings above this point.
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b5145f926abd..4267ba2ff0b7 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -289,6 +289,12 @@
<!-- Query all packages on device on R+ -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <queries>
+ <intent>
+ <action android:name="android.intent.action.NOTES" />
+ </intent>
+ </queries>
+
<!-- Permission to register process observer -->
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md
index 38d636d7ff82..95b986faebb4 100644
--- a/packages/SystemUI/docs/device-entry/quickaffordance.md
+++ b/packages/SystemUI/docs/device-entry/quickaffordance.md
@@ -8,7 +8,7 @@ credit card, etc.
### Step 1: create a new quick affordance config
* Create a new class under the [systemui/keyguard/domain/quickaffordance](../../src/com/android/systemui/keyguard/domain/quickaffordance) directory
* Please make sure that the class is injected through the Dagger dependency injection system by using the `@Inject` annotation on its main constructor and the `@SysUISingleton` annotation at class level, to make sure only one instance of the class is ever instantiated
-* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
+* Have the class implement the [KeyguardQuickAffordanceConfig](../../src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt) interface, notes:
* The `state` Flow property must emit `State.Hidden` when the feature is not enabled!
* It is safe to assume that `onQuickAffordanceClicked` will not be invoked if-and-only-if the previous rule is followed
* When implementing `onQuickAffordanceClicked`, the implementation can do something or it can ask the framework to start an activity using an `Intent` provided by the implementation
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index d5e84f9e30eb..d5552f6ad44f 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon word vereis nadat toestel herbegin het"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN word vereis nadat toestel herbegin het"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wagwoord word vereis nadat toestel herbegin het"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon word vir bykomende sekuriteit vereis"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN word vir bykomende sekuriteit vereis"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wagwoord word vir bykomende sekuriteit vereis"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Toestel is deur administrateur gesluit"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Toestel is handmatig gesluit"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie herken nie"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Verstek"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Borrel"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index be52c448181d..533e5a299e4c 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"መሣሪያ ዳግም ከጀመረ በኋላ ሥርዓተ ጥለት ያስፈልጋል"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"መሣሪያ ዳግም ከተነሳ በኋላ ፒን ያስፈልጋል"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"መሣሪያ ዳግም ከጀመረ በኋላ የይለፍ ቃል ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ሥርዓተ ጥለት ለተጨማሪ ደህንነት ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ፒን ለተጨማሪ ደህንነት ያስፈልጋል"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"የይለፍ ቃል ለተጨማሪ ደህንነት ያስፈልጋል"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"መሣሪያ በአስተዳዳሪ ተቆልፏል"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"መሣሪያ በተጠቃሚው ራሱ ተቆልፏል"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"አልታወቀም"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ነባሪ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"አረፋ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"አናሎግ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index adb57b6d4d67..81ce7d3c9361 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"يجب رسم النقش بعد إعادة تشغيل الجهاز"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"يجب إدخال رقم التعريف الشخصي بعد إعادة تشغيل الجهاز"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"يجب إدخال كلمة المرور بعد إعادة تشغيل الجهاز"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"يجب رسم النقش لمزيد من الأمان"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"يجب إدخال رقم التعريف الشخصي لمزيد من الأمان"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"يجب إدخال كلمة المرور لمزيد من الأمان"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"اختار المشرف قفل الجهاز"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"تم حظر الجهاز يدويًا"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"لم يتم التعرّف عليه."</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"تلقائي"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"فقاعة"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ساعة تقليدية"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index cbfb325fa9b3..443f666c0faa 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিৰিক্ত সুৰক্ষাৰ বাবে আর্হি দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিৰিক্ত সুৰক্ষাৰ বাবে পিন দিয়াটো বাধ্যতামূলক"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিৰিক্ত সুৰক্ষাৰ বাবে পাছৱর্ড দিয়াটো বাধ্যতামূলক"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্ৰশাসকে ডিভাইচ লক কৰি ৰাখিছে"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইচটো মেনুৱেলভাৱে লক কৰা হৈছিল"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"চিনাক্ত কৰিব পৰা নাই"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ডিফ’ল্ট"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"এনাল’গ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index 6ec1061ac8bb..e12569715eca 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yenidən başladıqdan sonra model tələb olunur"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıqdan sonra PIN tələb olunur"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıqdan sonra parol tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Əlavə təhlükəsizlik üçün model tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Əlavə təhlükəsizlik üçün PIN tələb olunur"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Əlavə təhlükəsizlik üçün parol tələb olunur"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz admin tərəfindən kilidlənib"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihaz əl ilə kilidləndi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmır"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Defolt"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Qabarcıq"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoq"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index 13d6613f0ff4..f0d1ef2d6bc3 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Treba da unesete šablon kada se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Treba da unesete PIN kada se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Treba da unesete lozinku kada se uređaj ponovo pokrene"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Treba da unesete šablon radi dodatne bezbednosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Treba da unesete PIN radi dodatne bezbednosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Treba da unesete lozinku radi dodatne bezbednosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznat"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Podrazumevani"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mehurići"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index 616d31ac6bee..e1af3eceada8 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Пасля перазапуску прылады патрабуецца ўзор"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Пасля перазапуску прылады патрабуецца PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Пасля перазапуску прылады патрабуецца пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для забеспячэння дадатковай бяспекі патрабуецца ўзор"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для забеспячэння дадатковай бяспекі патрабуецца PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для забеспячэння дадатковай бяспекі патрабуецца пароль"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Прылада заблакіравана адміністратарам"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Прылада была заблакіравана ўручную"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распазнана"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандартны"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бурбалкі"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Са стрэлкамі"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index 366a7f4cb817..0b4417ac30e8 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"След рестартиране на устройството се изисква фигура"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"След рестартиране на устройството се изисква ПИН код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"След рестартиране на устройството се изисква парола"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"За допълнителна сигурност се изисква фигура"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"За допълнителна сигурност се изисква ПИН код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"За допълнителна сигурност се изисква парола"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройството е заключено от администратора"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройството бе заключено ръчно"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не е разпознато"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандартен"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Балонен"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогов"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index c20be5d70bc7..485157984749 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইসটি পুনরায় চালু হওয়ার পর প্যাটার্নের প্রয়োজন হবে"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইসটি পুনরায় চালু হওয়ার পর পিন প্রয়োজন হবে"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইসটি পুনরায় চালু হওয়ার পর পাসওয়ার্ডের প্রয়োজন হবে"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"অতিরিক্ত সুরক্ষার জন্য প্যাটার্ন দেওয়া প্রয়োজন"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"অতিরিক্ত সুরক্ষার জন্য পিন দেওয়া প্রয়োজন"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"অতিরিক্ত সুরক্ষার জন্য পাসওয়ার্ড দেওয়া প্রয়োজন"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্রশাসক ডিভাইসটি লক করেছেন"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইসটিকে ম্যানুয়ালি লক করা হয়েছে"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"শনাক্ত করা যায়নি"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ডিফল্ট"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index f1c00a91a734..4705b4d9645f 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Uzorak je potreban radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN je potreban radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lozinka je potrebna radi dodatne sigurnosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Uređaj je zaključao administrator"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurići"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 709407c128e0..284eaebfbd8e 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cal introduir el patró quan es reinicia el dispositiu"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cal introduir el PIN quan es reinicia el dispositiu"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cal introduir la contrasenya quan es reinicia el dispositiu"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Cal introduir el patró per disposar de més seguretat"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Cal introduir el PIN per disposar de més seguretat"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Cal introduir la contrasenya per disposar de més seguretat"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'administrador ha bloquejat el dispositiu"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bombolla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analògica"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index a44658cc1de9..6b4f60742964 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po restartování zařízení je vyžadováno gesto"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po restartování zařízení je vyžadován kód PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po restartování zařízení je vyžadováno heslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pro ještě lepší zabezpečení je vyžadováno gesto"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pro ještě lepší zabezpečení je vyžadován kód PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pro ještě lepší zabezpečení je vyžadováno heslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zařízení je uzamknuto administrátorem"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zařízení bylo ručně uzamčeno"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznáno"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Výchozí"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 331c35579c7b..85238dfdab0f 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du skal angive et mønster, når du har genstartet enheden"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Der skal angives en pinkode efter genstart af enheden"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Der skal angives en adgangskode efter genstart af enheden"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Der kræves et mønster som ekstra beskyttelse"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Der kræves en pinkode som ekstra beskyttelse"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Der kræves en adgangskode som ekstra beskyttelse"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheden er blevet låst af administratoren"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheden blev låst manuelt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke genkendt"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index c19b357b5985..18befed429a9 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nach dem Neustart des Geräts ist die Eingabe des Musters erforderlich"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nach dem Neustart des Geräts ist die Eingabe der PIN erforderlich"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nach dem Neustart des Geräts ist die Eingabe des Passworts erforderlich"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zur Verbesserung der Sicherheit ist ein Muster erforderlich"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zur Verbesserung der Sicherheit ist eine PIN erforderlich"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zur Verbesserung der Sicherheit ist ein Passwort erforderlich"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Gerät vom Administrator gesperrt"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Gerät manuell gesperrt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nicht erkannt"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 1d6ec8240a9f..65b844862a9f 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Απαιτείται μοτίβο μετά από την επανεκκίνηση της συσκευής"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Απαιτείται PIN μετά από την επανεκκίνηση της συσκευής"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Απαιτείται κωδικός πρόσβασης μετά από την επανεκκίνηση της συσκευής"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Απαιτείται μοτίβο για πρόσθετη ασφάλεια"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Απαιτείται PIN για πρόσθετη ασφάλεια"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Απαιτείται κωδικός πρόσβασης για πρόσθετη ασφάλεια"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Η συσκευή κλειδώθηκε από τον διαχειριστή"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Η συσκευή κλειδώθηκε με μη αυτόματο τρόπο"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Δεν αναγνωρίστηκε"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Προεπιλογή"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 2b78f9678415..588f1b501f53 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index e1c253202c60..08fc8d66c98d 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 2b78f9678415..588f1b501f53 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 2b78f9678415..588f1b501f53 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pattern required for additional security"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN required for additional security"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password required for additional security"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
index 9052e4f04e54..a23aeb0822f3 100644
--- a/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rXC/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‏‎‏‎‎‎‏‎‎Pattern required after device restarts‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‎PIN required after device restarts‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎Password required after device restarts‎‏‎‎‏‎"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎Pattern required for additional security‎‏‎‎‏‎"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎PIN required for additional security‎‏‎‎‏‎"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‎Password required for additional security‎‏‎‎‏‎"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎For additional security, use pattern instead‎‏‎‎‏‎"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‎‎‎For additional security, use PIN instead‎‏‎‎‏‎"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎For additional security, use password instead‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‎Device locked by admin‎‏‎‎‏‎"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‎Device was locked manually‎‏‎‎‏‎"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎Not recognized‎‏‎‎‏‎"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎Default‎‏‎‎‏‎"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‏‎Bubble‎‏‎‎‏‎"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‎‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎Analog‎‏‎‎‏‎"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎Unlock your device to continue‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index 9dc054a53393..c71a67865925 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Se requiere el patrón después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Se requiere el PIN después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Se requiere la contraseña después de reiniciar el dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Se requiere el patrón por razones de seguridad"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Se requiere el PIN por razones de seguridad"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Se requiere la contraseña por razones de seguridad"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se bloqueó de forma manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoció"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index f9f0452771af..c6ee6980ff3b 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Debes introducir el patrón después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Debes introducir el PIN después de reiniciar el dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Debes introducir la contraseña después de reiniciar el dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Debes introducir el patrón como medida de seguridad adicional"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Debes introducir el PIN como medida de seguridad adicional"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Debes introducir la contraseña como medida de seguridad adicional"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se ha bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoce"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index dceb78efaca7..071ede8a0b25 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Lisaturvalisuse huvides tuleb sisestada muster"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Lisaturvalisuse huvides tuleb sisestada PIN-kood"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Lisaturvalisuse huvides tuleb sisestada parool"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administraator lukustas seadme"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Seade lukustati käsitsi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tuvastatud"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Vaikenumbrilaud"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mull"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 8431268464eb..9b8e65b1dde7 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Eredua marraztu beharko duzu gailua berrabiarazten denean"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PINa idatzi beharko duzu gailua berrabiarazten denean"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Eredua behar da gailua babestuago izateko"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PINa behar da gailua babestuago izateko"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Pasahitza behar da gailua babestuago izateko"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Puxikak"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogikoa"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 37bb260b28d3..3583f1e60e7a 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"بعد از بازنشانی دستگاه باید الگو وارد شود"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"بعد از بازنشانی دستگاه باید پین وارد شود"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"بعد از بازنشانی دستگاه باید گذرواژه وارد شود"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"برای ایمنی بیشتر باید الگو وارد شود"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"برای ایمنی بیشتر باید پین وارد شود"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"برای ایمنی بیشتر باید گذرواژه وارد شود"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"دستگاه توسط سرپرست سیستم قفل شده است"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"دستگاه به‌صورت دستی قفل شده است"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"شناسایی نشد"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"پیش‌فرض"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index f8cec421844c..a0ac6df2f029 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kuvio vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-koodi vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Salasana vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kuvio vaaditaan suojauksen parantamiseksi."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-koodi vaaditaan suojauksen parantamiseksi."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Salasana vaaditaan suojauksen parantamiseksi."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Järjestelmänvalvoja lukitsi laitteen."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Laite lukittiin manuaalisesti"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tunnistettu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Oletus"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Kupla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoginen"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
index 077fe11be4f2..66fd7c08f0e2 100644
--- a/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr-rCA/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Le schéma est exigé après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Le NIP est exigé après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Le mot de passe est exigé après le redémarrage de l\'appareil"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Le schéma est exigé pour plus de sécurité"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Le NIP est exigé pour plus de sécurité"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Le mot de passe est exigé pour plus de sécurité"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pour plus de sécurité, utilisez plutôt un schéma"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pour plus de sécurité, utilisez plutôt un NIP"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pour plus de sécurité, utilisez plutôt un mot de passe"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'appareil a été verrouillé par l\'administrateur"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"L\'appareil a été verrouillé manuellement"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Doigt non reconnu"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Déverrouiller votre appareil pour continuer"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index 45dadc1dfdc4..ec00ba3ae887 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Veuillez dessiner le schéma après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Veuillez saisir le code après le redémarrage de l\'appareil"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Veuillez saisir le mot de passe après le redémarrage de l\'appareil"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Veuillez dessiner le schéma pour renforcer la sécurité"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Veuillez saisir le code pour renforcer la sécurité"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Veuillez saisir le mot de passe pour renforcer la sécurité"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Appareil verrouillé par l\'administrateur"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Appareil verrouillé manuellement"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non reconnu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 4fbdd676d23a..a3f8e86cd5e6 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necesario o padrón despois do reinicio do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necesario o PIN despois do reinicio do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necesario o contrasinal despois do reinicio do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"É necesario o padrón para obter seguranza adicional"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"É necesario o PIN para obter seguranza adicional"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"É necesario o contrasinal para obter seguranza adicional"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"O administrador bloqueou o dispositivo"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo bloqueouse manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non se recoñeceu"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbulla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analóxico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index 6caac8a89a66..c97fe017ec60 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પૅટર્ન જરૂરી છે"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પિન જરૂરી છે"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પાસવર્ડ જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"વધારાની સુરક્ષા માટે પૅટર્ન જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"વધારાની સુરક્ષા માટે પિન જરૂરી છે"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"વધારાની સુરક્ષા માટે પાસવર્ડ જરૂરી છે"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"વ્યવસ્થાપકે ઉપકરણ લૉક કરેલું છે"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ઉપકરણ મેન્યુઅલી લૉક કર્યું હતું"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ઓળખાયેલ નથી"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ડિફૉલ્ટ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"બબલ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"એનાલોગ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 627576e1af66..128300488f27 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिवाइस फिर से चालू होने के बाद पैटर्न ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिवाइस फिर से चालू होने के बाद पिन ज़रूरी है"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिवाइस फिर से चालू होने के बाद पासवर्ड ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षा के लिए पैटर्न ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षा के लिए पिन ज़रूरी है"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षा के लिए पासवर्ड ज़रूरी है"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"व्यवस्थापक ने डिवाइस को लॉक किया है"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिवाइस को मैन्युअल रूप से लॉक किया गया था"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"पहचान नहीं हो पाई"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डिफ़ॉल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"एनालॉग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 8b1b5042f7f6..7a14a80e9bb3 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nakon ponovnog pokretanja uređaja morate unijeti uzorak"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nakon ponovnog pokretanja uređaja morate unijeti PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nakon ponovnog pokretanja uređaja morate unijeti zaporku"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Unesite uzorak radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Unesite PIN radi dodatne sigurnosti"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Unesite zaporku radi dodatne sigurnosti"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurić"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index 6b75e722dbfc..a4fbf537d331 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Az eszköz újraindítását követően meg kell adni a mintát"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Az eszköz újraindítását követően meg kell adni a PIN-kódot"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Az eszköz újraindítását követően meg kell adni a jelszót"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"A nagyobb biztonság érdekében minta szükséges"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"A nagyobb biztonság érdekében PIN-kód szükséges"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A nagyobb biztonság érdekében jelszó szükséges"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"A rendszergazda zárolta az eszközt"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Az eszközt manuálisan lezárták"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nem ismerhető fel"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Alapértelmezett"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Buborék"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analóg"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 3412026b90f3..086eeb939941 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել նախշը"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել PIN կոդը"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել նախշը"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել PIN կոդը"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Անվտանգության նկատառումներից ելնելով անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Սարքը կողպված է ադմինիստրատորի կողմից"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Սարքը կողպվել է ձեռքով"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Չհաջողվեց ճանաչել"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Կանխադրված"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Պղպջակ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Անալոգային"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index 1afb7918dd83..b43a0322fc7a 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pola diperlukan setelah perangkat dimulai ulang"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah perangkat dimulai ulang"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Sandi diperlukan setelah perangkat dimulai ulang"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Pola diperlukan untuk keamanan tambahan"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keamanan tambahan"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Sandi diperlukan untuk keamanan tambahan"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Perangkat dikunci oleh admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Perangkat dikunci secara manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 6abdc82ceecd..8bad961dae4b 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Mynsturs er krafist þegar tækið er endurræst"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-númers er krafist þegar tækið er endurræst"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Aðgangsorðs er krafist þegar tækið er endurræst"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mynsturs er krafist af öryggisástæðum"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN-númers er krafist af öryggisástæðum"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Aðgangsorðs er krafist af öryggisástæðum"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Kerfisstjóri læsti tæki"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Tækinu var læst handvirkt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Þekktist ekki"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Sjálfgefið"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Blaðra"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Með vísum"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 9fed5f72afa1..186177ff6d49 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Sequenza obbligatoria dopo il riavvio del dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN obbligatorio dopo il riavvio del dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password obbligatoria dopo il riavvio del dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Sequenza obbligatoria per maggiore sicurezza"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN obbligatorio per maggiore sicurezza"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Password obbligatoria per maggiore sicurezza"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloccato dall\'amministratore"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Il dispositivo è stato bloccato manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Non riconosciuto"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predefinito"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogico"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index b5b1c533778b..aab42069590b 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"יש להזין את קו ביטול הנעילה לאחר הפעלה מחדש של המכשיר"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"צריך להזין קוד אימות לאחר הפעלה מחדש של המכשיר"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"יש להזין סיסמה לאחר הפעלה מחדש של המכשיר"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"יש להזין את קו ביטול הנעילה כדי להגביר את רמת האבטחה"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"יש להזין קוד אימות כדי להגביר את רמת האבטחה"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"יש להזין סיסמה כדי להגביר את רמת האבטחה"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקו ביטול נעילה במקום זאת"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"כדי להגביר את רמת האבטחה, כדאי להשתמש בקוד אימות במקום זאת"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"כדי להגביר את רמת האבטחה, כדאי להשתמש בסיסמה במקום זאת"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"המנהל של המכשיר נהל אותו"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"המכשיר ננעל באופן ידני"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"לא זוהתה"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ברירת מחדל"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"בועה"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"אנלוגי"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"צריך לבטל את הנעילה של המכשיר כדי להמשיך"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ja/strings.xml b/packages/SystemUI/res-keyguard/values-ja/strings.xml
index afe0159ca1d6..1a4fb0b8e243 100644
--- a/packages/SystemUI/res-keyguard/values-ja/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ja/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"デバイスの再起動後はパターンの入力が必要となります"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"デバイスの再起動後は PIN の入力が必要となります"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"デバイスの再起動後はパスワードの入力が必要となります"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"追加の確認のためパターンが必要です"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"追加の確認のため PIN が必要です"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"追加の確認のためパスワードが必要です"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"セキュリティを強化するには代わりにパターンを使用してください"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"セキュリティを強化するには代わりに PIN を使用してください"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"セキュリティを強化するには代わりにパスワードを使用してください"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"デバイスは管理者によりロックされています"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"デバイスは手動でロックされました"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"認識されませんでした"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"デフォルト"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"バブル"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"アナログ"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"続行するにはデバイスのロックを解除してください"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index b32caa75f453..123cc39e47da 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა ნიმუშის დახატვა"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა PIN-კოდის შეყვანა"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა პაროლის შეყვანა"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"დამატებითი უსაფრთხოებისთვის საჭიროა ნიმუშის დახატვა"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"დამატებითი უსაფრთხოებისთვის საჭიროა PIN-კოდის შეყვანა"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"დამატებითი უსაფრთხოებისთვის საჭიროა პაროლის შეყვანა"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"მოწყობილობა ჩაკეტილია ადმინისტრატორის მიერ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"მოწყობილობა ხელით ჩაიკეტა"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"არ არის ამოცნობილი"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ნაგულისხმევი"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ბუშტი"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ანალოგური"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index d6d5bcdd4beb..8daca5c6d59a 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Құрылғы қайта іске қосылғаннан кейін, өрнекті енгізу қажет"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Құрылғы қайта іске қосылғаннан кейін, PIN кодын енгізу қажет"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Құрылғы қайта іске қосылғаннан кейін, құпия сөзді енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Қауіпсіздікті күшейту үшін өрнекті енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Қауіпсіздікті күшейту үшін PIN кодын енгізу қажет"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Қауіпсіздікті күшейту үшін құпия сөзді енгізу қажет"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Құрылғыны әкімші құлыптаған"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Құрылғы қолмен құлыпталды"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Танылмады"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Әдепкі"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Көпіршік"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогтық"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 00bfe05ada14..73f507c91ece 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"តម្រូវឲ្យប្រើលំនាំ ដើម្បីទទួលបានសវុត្ថិភាពបន្ថែម"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"តម្រូវឲ្យបញ្ចូលកូដ PIN ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ ដើម្បីទទួលបានសុវត្ថិភាពបន្ថែម"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ឧបករណ៍​ត្រូវបាន​ចាក់សោ​ដោយអ្នក​គ្រប់គ្រង"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកប្រើផ្ទាល់"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"មិនអាចសម្គាល់បានទេ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"លំនាំដើម"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ពពុះ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"អាណាឡូក"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index 80a98e6ff6d7..c279ceac244e 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಿನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿದೆ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗೆ ಪಿನ್ ಅಗತ್ಯವಿದೆ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿದೆ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ನಿರ್ವಾಹಕರು ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಿದ್ದಾರೆ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ಸಾಧನವನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಲಾಕ್‌ ಮಾಡಲಾಗಿದೆ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ಡೀಫಾಲ್ಟ್"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ಬಬಲ್"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ಅನಲಾಗ್"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index b952f1bba2dd..4c058edbf688 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"기기가 다시 시작되면 패턴이 필요합니다."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"기기가 다시 시작되면 PIN이 필요합니다."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"기기가 다시 시작되면 비밀번호가 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"보안 강화를 위해 패턴이 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"보안 강화를 위해 PIN이 필요합니다."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"보안 강화를 위해 비밀번호가 필요합니다."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"관리자가 기기를 잠갔습니다."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"기기가 수동으로 잠금 설정되었습니다."</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"인식할 수 없음"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"기본"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"버블"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"아날로그"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 485337d86408..7c7099e1cf61 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Түзмөк кайра күйгүзүлгөндөн кийин графикалык ачкычты тартуу талап кылынат"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Түзмөк кайра күйгүзүлгөндөн кийин PIN-кодду киргизүү талап кылынат"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Түзмөк кайра күйгүзүлгөндөн кийин сырсөздү киргизүү талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Коопсуздукту бекемдөө үчүн графикалык ачкыч талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Коопсуздукту бекемдөө үчүн PIN-код талап кылынат"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Коопсуздукту бекемдөө үчүн сырсөз талап кылынат"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Түзмөктү администратор кулпулап койгон"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Түзмөк кол менен кулпуланды"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Таанылган жок"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Демейки"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Көбүк"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналог"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index 17584b5a74fe..5a6df42884b4 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ຈຳເປັນຕ້ອງມີແບບຮູບປົດລັອກຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ຈຳເປັນຕ້ອງມີ PIN ຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ຈຳເປັນຕ້ອງມີແບບຮູບເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ຈຳເປັນຕ້ອງມີ PIN ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ອຸປະກອນຖືກລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ອຸປະກອນຖືກສັ່ງໃຫ້ລັອກ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ບໍ່ຮູ້ຈັກ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ຄ່າເລີ່ມຕົ້ນ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ຟອງ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ໂມງເຂັມ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index a066a66ec6a4..4d98fd17baf8 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iš naujo paleidus įrenginį būtinas atrakinimo piešinys"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iš naujo paleidus įrenginį būtinas PIN kodas"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iš naujo paleidus įrenginį būtinas slaptažodis"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Norint užtikrinti papildomą saugą būtinas atrakinimo piešinys"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Norint užtikrinti papildomą saugą būtinas PIN kodas"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Norint užtikrinti papildomą saugą būtinas slaptažodis"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Įrenginį užrakino administratorius"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Įrenginys užrakintas neautomatiškai"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Neatpažinta"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Numatytasis"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Debesėlis"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoginis"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index d371a4b9cbab..2660a069c949 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pēc ierīces restartēšanas ir jāievada atbloķēšanas kombinācija."</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pēc ierīces restartēšanas ir jāievada PIN kods."</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pēc ierīces restartēšanas ir jāievada parole."</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Papildu drošībai ir jāievada atbloķēšanas kombinācija."</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Papildu drošībai ir jāievada PIN kods."</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Papildu drošībai ir jāievada parole."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrators bloķēja ierīci."</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Ierīce tika bloķēta manuāli."</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nav atpazīts"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Noklusējums"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Burbuļi"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogais"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mk/strings.xml b/packages/SystemUI/res-keyguard/values-mk/strings.xml
index ef22564318e8..77e1b50d7a79 100644
--- a/packages/SystemUI/res-keyguard/values-mk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mk/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Потребна е шема по рестартирање на уредот"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Потребен е PIN-код по рестартирање на уредот"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Потребна е лозинка по рестартирање на уредот"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Потребна е шема за дополнителна безбедност"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Потребен е PIN-код за дополнителна безбедност"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Потребна е лозинка за дополнителна безбедност"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За дополнителна безбедност, користете шема"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За дополнителна безбедност, користете PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За дополнителна безбедност, користете лозинка"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Уредот е заклучен од администраторот"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уредот е заклучен рачно"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Непознат"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Стандарден"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Балонче"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналоген"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Отклучете го уредот за да продолжите"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index 63a542a63e04..e62b4356822d 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാറ്റേൺ വരയ്‌ക്കേണ്ടതുണ്ട്"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പിൻ നൽകേണ്ടതുണ്ട്"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാസ്‌വേഡ് നൽകേണ്ടതുണ്ട്"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"അധിക സുരക്ഷയ്ക്ക് പാറ്റേൺ ആവശ്യമാണ്"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"അധിക സുരക്ഷയ്ക്ക് പിൻ ആവശ്യമാണ്"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"അധിക സുരക്ഷയ്ക്ക് പാസ്‌വേഡ് ആവശ്യമാണ്"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ഉപകരണം അഡ്‌മിൻ ലോക്കുചെയ്തു"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ഉപകരണം നേരിട്ട് ലോക്കുചെയ്തു"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"തിരിച്ചറിയുന്നില്ല"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ഡിഫോൾട്ട്"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ബബിൾ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"അനലോഗ്"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mn/strings.xml b/packages/SystemUI/res-keyguard/values-mn/strings.xml
index 71c913f988bd..f2cc5ab195a0 100644
--- a/packages/SystemUI/res-keyguard/values-mn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mn/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Төхөөрөмжийг дахин эхлүүлсний дараа загвар оруулах шаардлагатай"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Төхөөрөмжийг дахин эхлүүлсний дараа ПИН оруулах шаардлагатай"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Төхөөрөмжийг дахин эхлүүлсний дараа нууц үг оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Аюулгүй байдлын үүднээс загвар оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Аюулгүй байдлын үүднээс ПИН оруулах шаардлагатай"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Аюулгүй байдлын үүднээс нууц үг оруулах шаардлагатай"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Нэмэлт аюулгүй байдлын үүднээс оронд нь хээ ашиглана уу"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Нэмэлт аюулгүй байдлын үүднээс оронд нь ПИН ашиглана уу"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Нэмэлт аюулгүй байдлын үүднээс оронд нь нууц үг ашиглана уу"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Админ төхөөрөмжийг түгжсэн"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Төхөөрөмжийг гараар түгжсэн"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Таньж чадсангүй"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Өгөгдмөл"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бөмбөлөг"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Aналог"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Үргэлжлүүлэхийн тулд төхөөрөмжийнхөө түгжээг тайлна уу"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 6ac13bdde13a..1454b20d3797 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिव्हाइस रीस्टार्ट झाल्यावर पॅटर्न आवश्यक आहे"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिव्हाइस रीस्टार्ट झाल्यावर पिन आवश्यक आहे"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षिततेसाठी पॅटर्न आवश्‍यक आहे"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षिततेसाठी पिन आवश्‍यक आहे"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षिततेसाठी पासवर्ड आवश्‍यक आहे"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकाद्वारे लॉक केलेले डिव्हाइस"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिव्हाइस मॅन्युअली लॉक केले होते"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ओळखले नाही"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डीफॉल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"अ‍ॅनालॉग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index 453afc3d6b37..a6d1af9368ec 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Corak diperlukan setelah peranti dimulakan semula"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah peranti dimulakan semula"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kata laluan diperlukan setelah peranti dimulakan semula"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Corak diperlukan untuk keselamatan tambahan"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN diperlukan untuk keselamatan tambahan"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kata laluan diperlukan untuk keselamatan tambahan"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Peranti dikunci oleh pentadbir"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Peranti telah dikunci secara manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Lalai"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Gelembung"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 1cc46b16ec3f..5617a1188a40 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပုံစံ လိုအပ်ပါသည်"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် စကားဝှက် လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပုံစံ လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ပိုမို၍ လုံခြုံမှု ရှိစေရန် စကားဝှက် လိုအပ်ပါသည်"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"မူလ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ပူဖောင်းကွက်"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ရိုးရိုး"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 5310a7301d4e..0ad9e951b1e4 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du må tegne mønsteret etter at enheten har startet på nytt"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du må skrive inn PIN-koden etter at enheten har startet på nytt"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du må skrive inn passordet etter at enheten har startet på nytt"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du må tegne mønsteret for ekstra sikkerhet"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du må skrive inn PIN-koden for ekstra sikkerhet"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du må skrive inn passordet for ekstra sikkerhet"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheten er låst av administratoren"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten ble låst manuelt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke gjenkjent"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 534164b3f2c9..196b74a5658b 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"यन्त्र पुनः सुरु भएपछि ढाँचा आवश्यक पर्दछ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"यन्त्र पुनः सुरु भएपछि PIN आवश्यक पर्दछ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"यन्त्र पुनः सुरु भएपछि पासवर्ड आवश्यक पर्दछ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"अतिरिक्त सुरक्षाको लागि ढाँचा आवश्यक छ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"अतिरिक्त सुरक्षाको लागि PIN आवश्यक छ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"अतिरिक्त सुरक्षाको लागि पासवर्ड आवश्यक छ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकले यन्त्रलाई लक गर्नुभएको छ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"यन्त्रलाई म्यानुअल तरिकाले लक गरिएको थियो"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"पहिचान भएन"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"डिफल्ट"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"एनालग"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 08e226d4ec07..747b3bbd9128 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -78,9 +78,12 @@
<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>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Patroon vereist voor extra beveiliging"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Pincode vereist voor extra beveiliging"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Wachtwoord vereist voor extra beveiliging"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Apparaat vergrendeld door beheerder"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Apparaat is handmatig vergrendeld"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Niet herkend"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standaard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bel"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-or/strings.xml b/packages/SystemUI/res-keyguard/values-or/strings.xml
index 3cdd2649d1b4..75f7a898585b 100644
--- a/packages/SystemUI/res-keyguard/values-or/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-or/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାଟର୍ନ ଆବଶ୍ୟକ ଅଟେ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବାପରେ ପାସ୍‌ୱର୍ଡ ଆବଶ୍ୟକ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ଡିଭାଇସ୍‍ ରିଷ୍ଟାର୍ଟ ହେବା ପରେ ପାସୱର୍ଡ ଆବଶ୍ୟକ ଅଟେ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାଟର୍ନ ଆବଶ୍ୟକ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ PIN ଆବଶ୍ୟକ ଅଟେ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ ପାସ୍‌ୱର୍ଡ ଆବଶ୍ୟକ"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାଟର୍ନ ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ PIN ବ୍ୟବହାର କରନ୍ତୁ"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ଅତିରିକ୍ତ ସୁରକ୍ଷା ପାଇଁ, ଏହା ପରିବର୍ତ୍ତେ ପାସୱାର୍ଡ ବ୍ୟବହାର କରନ୍ତୁ"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ଡିଭାଇସ୍‍ ଆଡମିନଙ୍କ ଦ୍ୱାରା ଲକ୍‍ କରାଯାଇଛି"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ଡିଭାଇସ୍‍ ମାନୁଆଲ ଭାବେ ଲକ୍‍ କରାଗଲା"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ଚିହ୍ନଟ ହେଲାନାହିଁ"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ଡିଫଲ୍ଟ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ବବଲ୍"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ଆନାଲଗ୍"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ଜାରି ରଖିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ଅନଲକ କରନ୍ତୁ"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index 409f72740649..bf1a359a2c75 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ਡੀਵਾਈਸ ਨੂੰ ਹੱਥੀਂ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"ਬੁਲਬੁਲਾ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ਐਨਾਲੌਗ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index 52bc98236330..c49149baa93a 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po ponownym uruchomieniu urządzenia wymagany jest wzór"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po ponownym uruchomieniu urządzenia wymagany jest kod PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po ponownym uruchomieniu urządzenia wymagane jest hasło"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Dla większego bezpieczeństwa musisz narysować wzór"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Dla większego bezpieczeństwa musisz podać kod PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Dla większego bezpieczeństwa musisz podać hasło"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Urządzenie zablokowane przez administratora"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Urządzenie zostało zablokowane ręcznie"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie rozpoznano"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Domyślna"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bąbelkowy"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogowy"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
index b9348260d40e..3d60e8c45bcb 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rBR/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
index a67bfb020a3c..0a943496fba9 100644
--- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necessário um padrão após reiniciar o dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necessário um PIN após reiniciar o dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necessária uma palavra-passe após reiniciar o dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Para segurança adicional, é necessário um padrão"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Para segurança adicional, é necessário um PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Para segurança adicional, é necessária uma palavra-passe"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para uma segurança adicional, use antes o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para uma segurança adicional, use antes o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para uma segurança adicional, use antes a palavra-passe"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo gestor"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido."</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predefinido"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balão"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-pt/strings.xml b/packages/SystemUI/res-keyguard/values-pt/strings.xml
index b9348260d40e..3d60e8c45bcb 100644
--- a/packages/SystemUI/res-keyguard/values-pt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pt/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"O padrão é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"O PIN é exigido após a reinicialização do dispositivo"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"A senha é exigida após a reinicialização do dispositivo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"O padrão é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"O PIN é necessário para aumentar a segurança"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"A senha é necessária para aumentar a segurança"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para ter mais segurança, use o padrão"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para ter mais segurança, use o PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para ter mais segurança, use a senha"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado pelo administrador"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo foi bloqueado manualmente"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Não reconhecido"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Padrão"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bolha"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueie o dispositivo para continuar"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 5ee67d917768..547224e781b1 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Parola este necesară după repornirea dispozitivului"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Modelul este necesar pentru securitate suplimentară"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Codul PIN este necesar pentru securitate suplimentară"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Parola este necesară pentru securitate suplimentară"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispozitiv blocat de administrator"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Dispozitivul a fost blocat manual"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nu este recunoscut"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Prestabilit"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogic"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 2b8f8d6a93f8..f1945ad3fb03 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"После перезагрузки устройства необходимо ввести графический ключ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"После перезагрузки устройства необходимо ввести PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"После перезагрузки устройства необходимо ввести пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"В качестве дополнительной меры безопасности введите графический ключ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"В качестве дополнительной меры безопасности введите PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"В качестве дополнительной меры безопасности введите пароль"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"В целях дополнительной безопасности используйте графический ключ"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"В целях дополнительной безопасности используйте PIN-код"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"В целях дополнительной безопасности используйте пароль"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройство заблокировано администратором"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройство было заблокировано вручную"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распознано"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"По умолчанию"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Пузырь"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Стрелки"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Чтобы продолжить, разблокируйте устройство"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index 4e911defffb6..e5862c358002 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"උපාංගය නැවත ආරම්භ වූ පසු රටාව අවශ්‍යයි"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"උපාංගය නැවත ආරම්භ වූ පසු PIN අංකය අවශ්‍යයි"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"උපාංගය නැවත ආරම්භ වූ පසු මුරපදය අවශ්‍යයි"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"අමතර ආරක්ෂාව සඳහා රටාව අවශ්‍යයි"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"අමතර ආරක්ෂාව සඳහා PIN අංකය අවශ්‍යයි"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"අමතර ආරක්ෂාව සඳහා මුරපදය අවශ්‍යයි"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ඔබගේ පරිපාලක විසින් උපාංගය අගුළු දමා ඇත"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"උපාංගය හස්තීයව අගුලු දමන ලදී"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"හඳුනා නොගන්නා ලදී"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"පෙරනිමි"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"බුබුළ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ප්‍රතිසමය"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index f2d68e3763d3..efe4ec864448 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po reštartovaní zariadenia musíte zadať bezpečnostný vzor"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po reštartovaní zariadenia musíte zadať kód PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po reštartovaní zariadenia musíte zadať heslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Na ďalšie zabezpečenie musíte zadať bezpečnostný vzor"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Na ďalšie zabezpečenie musíte zadať kód PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Na ďalšie zabezpečenie musíte zadať heslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zariadenie zamkol správca"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zariadenie bolo uzamknuté ručne"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznané"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Predvolený"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analógový"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 772308f9f658..52726c225498 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po vnovičnem zagonu naprave je treba vnesti vzorec"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po vnovičnem zagonu naprave je treba vnesti kodo PIN"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po vnovičnem zagonu naprave je treba vnesti geslo"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Zaradi dodatne varnosti morate vnesti vzorec"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Zaradi dodatne varnosti morate vnesti kodo PIN"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Zaradi dodatne varnosti morate vnesti geslo"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Napravo je zaklenil skrbnik"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Naprava je bila ročno zaklenjena"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ni prepoznano"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Privzeto"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Mehurček"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogno"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index c7584622823c..a0a55944eced 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kërkohet motivi pas rinisjes së pajisjes"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kërkohet kodi PIN pas rinisjes së pajisjes"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kërkohet fjalëkalimi pas rinisjes së pajisjes"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kërkohet motivi për më shumë siguri"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kërkohet kodi PIN për më shumë siguri"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kërkohet fjalëkalimi për më shumë siguri"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Pajisja është e kyçur nga administratori"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Pajisja është kyçur manualisht"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Nuk njihet"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"E parazgjedhur"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Flluskë"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analoge"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index e6fe8531ff4c..e634fdcb586e 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Треба да унесете шаблон када се уређај поново покрене"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Треба да унесете PIN када се уређај поново покрене"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Треба да унесете лозинку када се уређај поново покрене"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Треба да унесете шаблон ради додатне безбедности"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Треба да унесете PIN ради додатне безбедности"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Треба да унесете лозинку ради додатне безбедности"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Администратор је закључао уређај"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уређај је ручно закључан"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Није препознат"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Подразумевани"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Мехурићи"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналогни"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index fa241d96cfae..fc9beb1286a6 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du måste rita mönster när du har startat om enheten"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du måste ange pinkod när du har startat om enheten"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du måste ange lösenord när du har startat om enheten"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Du måste rita mönster för ytterligare säkerhet"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Du måste ange pinkod för ytterligare säkerhet"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Du måste ange lösenord för ytterligare säkerhet"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratören har låst enheten"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten har låsts manuellt"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Identifierades inte"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubbla"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index 791bceb071e9..bcab24b013bb 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Unafaa kuchora mchoro baada ya kuwasha kifaa upya"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Unafaa kuweka PIN baada ya kuwasha kifaa upya"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Unafaa kuweka nenosiri baada ya kuwasha kifaa upya"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Mchoro unahitajika ili kuongeza usalama"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"PIN inahitajika ili kuongeza usalama"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Nenosiri linahitajika ili kuongeza usalama."</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Msimamizi amefunga kifaa"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Umefunga kifaa mwenyewe"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Haitambuliwi"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Chaguomsingi"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Kiputo"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogi"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 271657d9f0a1..88d5760e7f6c 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"சாதனத்தை மீண்டும் தொடங்கியதும், பேட்டர்னை வரைய வேண்டும்"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"சாதனத்தை மீண்டும் தொடங்கியதும், பின்னை உள்ளிட வேண்டும்"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"சாதனத்தை மீண்டும் தொடங்கியதும், கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"கூடுதல் பாதுகாப்பிற்கு, பேட்டர்னை வரைய வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"கூடுதல் பாதுகாப்பிற்கு, பின்னை உள்ளிட வேண்டும்"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"கூடுதல் பாதுகாப்பிற்கு, கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"நிர்வாகி சாதனத்தைப் பூட்டியுள்ளார்"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"பயனர் சாதனத்தைப் பூட்டியுள்ளார்"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"அடையாளங்காணபடவில்லை"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"இயல்பு"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"பபிள்"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"அனலாக்"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index f62e667ee26d..3a0111a193bd 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత నమూనాను గీయాలి"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"డివైజ్‌ను పునఃప్రారంభించిన తర్వాత పిన్ నమోదు చేయాలి"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత పాస్‌వర్డ్‌ను నమోదు చేయాలి"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"అదనపు సెక్యూరిటీ కోసం ఆకృతి అవసరం"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"అదనపు సెక్యూరిటీ కోసం పిన్ ఎంటర్ చేయాలి"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"అదనపు సెక్యూరిటీ కోసం పాస్‌వర్డ్‌ను ఎంటర్ చేయాలి"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"పరికరం నిర్వాహకుల ద్వారా లాక్ చేయబడింది"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"పరికరం మాన్యువల్‌గా లాక్ చేయబడింది"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"గుర్తించలేదు"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ఆటోమేటిక్"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"బబుల్"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"ఎనలాగ్"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-th/strings.xml b/packages/SystemUI/res-keyguard/values-th/strings.xml
index 62a83bcf9d7a..14a65a074f87 100644
--- a/packages/SystemUI/res-keyguard/values-th/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-th/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ต้องวาดรูปแบบหลังจากอุปกรณ์รีสตาร์ท"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ต้องระบุ PIN หลังจากอุปกรณ์รีสตาร์ท"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ต้องป้อนรหัสผ่านหลังจากอุปกรณ์รีสตาร์ท"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"ต้องวาดรูปแบบเพื่อความปลอดภัยเพิ่มเติม"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"ต้องระบุ PIN เพื่อความปลอดภัยเพิ่มเติม"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"ต้องป้อนรหัสผ่านเพื่อความปลอดภัยเพิ่มเติม"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ใช้รูปแบบแทนเพื่อเพิ่มความปลอดภัย"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ใช้ PIN แทนเพื่อเพิ่มความปลอดภัย"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ใช้รหัสผ่านแทนเพื่อเพิ่มความปลอดภัย"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ผู้ดูแลระบบล็อกอุปกรณ์"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"มีการล็อกอุปกรณ์ด้วยตัวเอง"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"ไม่รู้จัก"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"ค่าเริ่มต้น"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"บับเบิล"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"แอนะล็อก"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ปลดล็อกอุปกรณ์ของคุณเพื่อดำเนินการต่อ"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-tl/strings.xml b/packages/SystemUI/res-keyguard/values-tl/strings.xml
index 524ea4782506..7936058581ae 100644
--- a/packages/SystemUI/res-keyguard/values-tl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tl/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kailangan ng pattern pagkatapos mag-restart ng device"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kailangan ng PIN pagkatapos mag-restart ng device"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kailangan ng password pagkatapos mag-restart ng device"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kinakailangan ang pattern para sa karagdagang seguridad"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kinakailangan ang PIN para sa karagdagang seguridad"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Kinakailangan ang password para sa karagdagang seguridad"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para sa karagdagang seguridad, gumamit na lang ng pattern"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para sa karagdagang seguridad, gumamit na lang ng PIN"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para sa karagdagang seguridad, gumamit na lang ng password"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Na-lock ng admin ang device"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Manual na na-lock ang device"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Hindi nakilala"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"I-unlock ang iyong device para magpatuloy"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index 54aaae38b18c..e5207623adc2 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yeniden başladıktan sonra desen gerekir"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıktan sonra PIN gerekir"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıktan sonra şifre gerekir"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Ek güvenlik için desen gerekir"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Ek güvenlik için PIN gerekir"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Ek güvenlik için şifre gerekir"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz, yönetici tarafından kilitlendi"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihazın manuel olarak kilitlendi"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmadı"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Varsayılan"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Baloncuk"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 6144c1c4e0d5..613181d6c96f 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Після перезавантаження пристрою потрібно ввести ключ"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Після перезавантаження пристрою потрібно ввести PIN-код"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Після перезавантаження пристрою потрібно ввести пароль"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Для додаткового захисту потрібно ввести ключ"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Для додаткового захисту потрібно ввести PIN-код"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Для додаткового захисту потрібно ввести пароль"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Адміністратор заблокував пристрій"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Пристрій заблоковано вручну"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Не розпізнано"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"За умовчанням"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Бульбашковий"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Аналоговий"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index 4e778413775e..a122f8537611 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"آلہ دوبارہ چالو ہونے کے بعد پیٹرن درکار ہوتا ہے"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‏آلہ دوبارہ چالو ہونے کے بعد PIN درکار ہوتا ہے"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"آلہ دوبارہ چالو ہونے کے بعد پاس ورڈ درکار ہوتا ہے"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"اضافی سیکیورٹی کیلئے پیٹرن درکار ہے"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"‏اضافی سیکیورٹی کیلئے PIN درکار ہے"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"اضافی سیکیورٹی کیلئے پاس ورڈ درکار ہے"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"ڈیفالٹ"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"بلبلہ"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"اینالاگ"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-uz/strings.xml b/packages/SystemUI/res-keyguard/values-uz/strings.xml
index afaf7464d091..2cc9724dc53b 100644
--- a/packages/SystemUI/res-keyguard/values-uz/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uz/strings.xml
@@ -78,9 +78,9 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Qurilma qayta ishga tushganidan keyin grafik kalitni kiritish zarur"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Qurilma qayta ishga tushganidan keyin PIN kodni kiritish zarur"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Qurilma qayta ishga tushganidan keyin parolni kiritish zarur"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Qo‘shimcha xavfsizlik chorasi sifatida grafik kalit talab qilinadi"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Qo‘shimcha xavfsizlik chorasi sifatida PIN kod talab qilinadi"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Qo‘shimcha xavfsizlik chorasi sifatida parol talab qilinadi"</string>
+ <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Qoʻshimcha xavfsizlik maqsadida oʻrniga grafik kalitdan foydalaning"</string>
+ <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Qoʻshimcha xavfsizlik maqsadida oʻrniga PIN koddan foydalaning"</string>
+ <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Qoʻshimcha xavfsizlik maqsadida oʻrniga paroldan foydalaning"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Qurilma administrator tomonidan bloklangan"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Qurilma qo‘lda qulflangan"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Aniqlanmadi"</string>
@@ -90,4 +90,5 @@
<string name="clock_title_default" msgid="6342735240617459864">"Odatiy"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Pufaklar"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
+ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Davom etish uchun qurilmangizni qulfdan chiqaring"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index 1d6cfa85e4ed..e7c9295815ad 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Yêu cầu hình mở khóa sau khi thiết bị khởi động lại"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Yêu cầu mã PIN sau khi thiết bị khởi động lại"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Yêu cầu mật khẩu sau khi thiết bị khởi động lại"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Yêu cầu hình mở khóa để bảo mật thêm"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Yêu cầu mã PIN để bảo mật thêm"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Yêu cầu mật khẩu để bảo mật thêm"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Thiết bị đã bị quản trị viên khóa"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Thiết bị đã bị khóa theo cách thủ công"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Không nhận dạng được"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Mặc định"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bong bóng"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Đồng hồ kim"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index 8c8507ed06fa..d37d645b15ee 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"重启设备后需要绘制解锁图案"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"重启设备后需要输入 PIN 码"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"重启设备后需要输入密码"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"需要绘制解锁图案以进一步确保安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"需要输入 PIN 码以进一步确保安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"需要输入密码以进一步确保安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理员已锁定设备"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"此设备已手动锁定"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"无法识别"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"默认"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"指针"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index c331a925be39..9dbb8f2dac73 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後,必須畫出上鎖圖案才能使用"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後,必須輸入 PIN 碼才能使用"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後,必須輸入密碼才能使用"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請務必畫出上鎖圖案,以進一步確保安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請務必輸入 PIN 碼,以進一步確保安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請務必輸入密碼,以進一步確保安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"裝置已由管理員鎖定"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"使用者已手動將裝置上鎖"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"未能識別"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"指針"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index 1e1bec3ef76a..ebb88e13194b 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後需要畫出解鎖圖案"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後需要輸入 PIN 碼"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後需要輸入密碼"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"請畫出解鎖圖案,以進一步確保資訊安全"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"請輸入 PIN 碼,以進一步確保資訊安全"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"請輸入密碼,以進一步確保資訊安全"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理員已鎖定裝置"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"裝置已手動鎖定"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"無法識別"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"類比"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index c8f78ea441f5..57e56f713536 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -78,9 +78,12 @@
<string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iphethini iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
<string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iphinikhodi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
<string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iphasiwedi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
- <string name="kg_prompt_reason_timeout_pattern" msgid="9170360502528959889">"Kudingeka iphethini ngokuvikeleka okungeziwe"</string>
- <string name="kg_prompt_reason_timeout_pin" msgid="5945186097160029201">"Kudingeka iphinikhodi ngokuvikeleka okungeziwe"</string>
- <string name="kg_prompt_reason_timeout_password" msgid="2258263949430384278">"Iphasiwedi idingelwa ukuvikela okungeziwe"</string>
+ <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
+ <skip />
+ <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
+ <skip />
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Idivayisi ikhiywe ngumlawuli"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Idivayisi ikhiywe ngokwenza"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Akwaziwa"</string>
@@ -90,4 +93,6 @@
<string name="clock_title_default" msgid="6342735240617459864">"Okuzenzekelayo"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Ibhamuza"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"I-Analog"</string>
+ <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
+ <skip />
</resources>
diff --git a/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml b/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml
new file mode 100644
index 000000000000..de83df4e625c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_floating_message_background.xml
@@ -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.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/accessibility_floating_menu_message_background"/>
+ <corners android:radius="28dp"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/overlay_badge_background.xml b/packages/SystemUI/res/drawable/overlay_badge_background.xml
new file mode 100644
index 000000000000..857632edcf0d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/overlay_badge_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="oval">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+</shape>
diff --git a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
index 3bcc37a478c9..e2ce34f5008e 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_password_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPasswordView
+<com.android.systemui.biometrics.ui.CredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -86,4 +86,4 @@
</LinearLayout>
-</com.android.systemui.biometrics.AuthCredentialPasswordView> \ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPasswordView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
index a3dd334bd667..6e0e38b95ee5 100644
--- a/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout-land/auth_credential_pattern_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPatternView
+<com.android.systemui.biometrics.ui.CredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -83,4 +83,4 @@
</FrameLayout>
-</com.android.systemui.biometrics.AuthCredentialPatternView> \ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPatternView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 774b335f913e..021ebe6e7bff 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPasswordView
+<com.android.systemui.biometrics.ui.CredentialPasswordView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -83,4 +83,4 @@
</LinearLayout>
-</com.android.systemui.biometrics.AuthCredentialPasswordView> \ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPasswordView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4af997017bba..891c6af4b667 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.biometrics.AuthCredentialPatternView
+<com.android.systemui.biometrics.ui.CredentialPatternView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -78,4 +78,4 @@
android:layout_gravity="center_horizontal|bottom"/>
</FrameLayout>
-</com.android.systemui.biometrics.AuthCredentialPatternView> \ No newline at end of file
+</com.android.systemui.biometrics.ui.CredentialPatternView>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 5b961595fbd3..ae2537fe29f6 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -190,6 +190,11 @@
</LinearLayout>
+ <ViewStub android:id="@+id/secondary_mobile_network_stub"
+ android:inflatedId="@+id/secondary_mobile_network_layout"
+ android:layout="@layout/qs_dialog_secondary_mobile_network"
+ style="@style/InternetDialog.Network"/>
+
<LinearLayout
android:id="@+id/turn_on_wifi_layout"
style="@style/InternetDialog.Network"
diff --git a/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml b/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml
new file mode 100644
index 000000000000..4592c5e6332c
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_dialog_secondary_mobile_network.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/InternetDialog.Network">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/secondary_signal_icon"
+ android:autoMirrored="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/secondary_mobile_title"
+ android:maxLines="1"
+ style="@style/InternetDialog.NetworkTitle"/>
+ <TextView
+ android:id="@+id/secondary_mobile_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/secondary_settings_icon"
+ android:src="@drawable/ic_settings_24dp"
+ android:layout_width="24dp"
+ android:layout_gravity="end|center_vertical"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
index 2fb6d6cb9bd5..9fc3f409642b 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_expanded_header.xml
@@ -23,6 +23,7 @@
android:layout_height="wrap_content"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:background="@android:color/transparent"
+ android:importantForAccessibility="no"
android:baselineAligned="false"
android:clickable="false"
android:clipChildren="false"
@@ -56,7 +57,7 @@
android:clipToPadding="false"
android:focusable="true"
android:paddingBottom="@dimen/qqs_layout_padding_bottom"
- android:importantForAccessibility="yes">
+ android:importantForAccessibility="no">
</com.android.systemui.qs.QuickQSPanel>
</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index 60bc3732cde0..8b5d953c3fe7 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -25,6 +25,7 @@
android:gravity="center"
android:layout_gravity="top"
android:orientation="horizontal"
+ android:importantForAccessibility="no"
android:clickable="true"
android:minHeight="48dp">
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index 9c027495aa1e..1ac78d491d78 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -103,8 +103,18 @@
app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
app:layout_constraintStart_toStartOf="@id/screenshot_preview_border"
app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"
- app:layout_constraintTop_toTopOf="@id/screenshot_preview_border">
- </ImageView>
+ app:layout_constraintTop_toTopOf="@id/screenshot_preview_border"/>
+ <ImageView
+ android:id="@+id/screenshot_badge"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:padding="4dp"
+ android:visibility="gone"
+ android:background="@drawable/overlay_badge_background"
+ android:elevation="8dp"
+ android:src="@drawable/overlay_cancel"
+ app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"
+ app:layout_constraintEnd_toEndOf="@id/screenshot_preview_border"/>
<FrameLayout
android:id="@+id/screenshot_dismiss_button"
android:layout_width="@dimen/overlay_dismiss_button_tappable_size"
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index dc2bee56373c..16152f80308a 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -99,6 +99,8 @@
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
+ <color name="accessibility_floating_menu_message_background">@*android:color/background_material_dark</color>
+ <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
<color name="people_tile_background">@color/material_dynamic_secondary20</color>
</resources>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 5f1863a69ef8..8a1a9e2cb4bf 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -672,7 +672,7 @@
<string name="data_connection_no_internet" msgid="691058178914184544">"沒有網際網路連線"</string>
<string name="accessibility_quick_settings_open_settings" msgid="536838345505030893">"開啟「<xliff:g id="ID_1">%s</xliff:g>」設定。"</string>
<string name="accessibility_quick_settings_edit" msgid="1523745183383815910">"編輯設定順序。"</string>
- <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源按鈕選單"</string>
+ <string name="accessibility_quick_settings_power_menu" msgid="6820426108301758412">"電源鍵選單"</string>
<string name="accessibility_quick_settings_page" msgid="7506322631645550961">"第 <xliff:g id="ID_1">%1$d</xliff:g> 頁,共 <xliff:g id="ID_2">%2$d</xliff:g> 頁"</string>
<string name="tuner_lock_screen" msgid="2267383813241144544">"鎖定畫面"</string>
<string name="thermal_shutdown_title" msgid="2702966892682930264">"手機先前過熱,因此關閉電源"</string>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 9e8bef06270b..55b59b63c2f9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -219,6 +219,8 @@
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#CCFFFFFF</color> <!-- 80% -->
<color name="accessibility_floating_menu_stroke_dark">#26FFFFFF</color> <!-- 15% -->
+ <color name="accessibility_floating_menu_message_background">@*android:color/background_material_light</color>
+ <color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_light</color>
<!-- Wallet screen -->
<color name="wallet_card_border">#33FFFFFF</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9188ce091a3b..93982cb2c5b9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -643,6 +643,18 @@
<item>26</item> <!-- MOUTH_COVERING_DETECTED -->
</integer-array>
+ <!-- Which device wake-ups will trigger face auth. These values correspond with
+ PowerManager#WakeReason. -->
+ <integer-array name="config_face_auth_wake_up_triggers">
+ <item>1</item> <!-- WAKE_REASON_POWER_BUTTON -->
+ <item>4</item> <!-- WAKE_REASON_GESTURE -->
+ <item>6</item> <!-- WAKE_REASON_WAKE_KEY -->
+ <item>7</item> <!-- WAKE_REASON_WAKE_MOTION -->
+ <item>9</item> <!-- WAKE_REASON_LID -->
+ <item>10</item> <!-- WAKE_REASON_DISPLAY_GROUP_ADDED -->
+ <item>12</item> <!-- WAKE_REASON_UNFOLD_DEVICE -->
+ </integer-array>
+
<!-- Whether the communal service should be enabled -->
<bool name="config_communalServiceEnabled">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 66f0e7543469..f02f29a4f741 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1336,6 +1336,14 @@
<dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen>
<dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
+ <dimen name="accessibility_floating_menu_message_container_horizontal_padding">15dp</dimen>
+ <dimen name="accessibility_floating_menu_message_text_vertical_padding">8dp</dimen>
+ <dimen name="accessibility_floating_menu_message_margin">8dp</dimen>
+ <dimen name="accessibility_floating_menu_message_elevation">5dp</dimen>
+ <dimen name="accessibility_floating_menu_message_text_size">14sp</dimen>
+ <dimen name="accessibility_floating_menu_message_min_width">312dp</dimen>
+ <dimen name="accessibility_floating_menu_message_min_height">48dp</dimen>
+
<dimen name="accessibility_floating_tooltip_arrow_width">8dp</dimen>
<dimen name="accessibility_floating_tooltip_arrow_height">16dp</dimen>
<dimen name="accessibility_floating_tooltip_arrow_margin">-2dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 7ca42f7d7015..4fd25a98a71c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -177,6 +177,7 @@
<item type="id" name="action_move_bottom_right"/>
<item type="id" name="action_move_to_edge_and_hide"/>
<item type="id" name="action_move_out_edge_and_show"/>
+ <item type="id" name="action_remove_menu"/>
<!-- rounded corner view id -->
<item type="id" name="rounded_corner_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d4d8843acdea..b325c56adefc 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2022,6 +2022,15 @@
<!-- Text used to refer to the user's current carrier in mobile_data_disable_message if the users's mobile network carrier name is not available [CHAR LIMIT=NONE] -->
<string name="mobile_data_disable_message_default_carrier">your carrier</string>
+ <!-- Title of the dialog to turn off data usage [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_disable_title">Switch back to <xliff:g id="carrier" example="T-Mobile">%s</xliff:g>?</string>
+ <!-- Message body of the dialog to turn off data usage [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_disable_message">Mobile data won\’t automatically switch based on availability</string>
+ <!-- Negative button title of the quick settings switch back to DDS dialog [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_dialog_negative_button">No thanks</string>
+ <!-- Positive button title of the quick settings switch back to DDS dialog [CHAR LIMIT=NONE] -->
+ <string name="auto_data_switch_dialog_positive_button">Yes, switch</string>
+
<!-- Warning shown when user input has been blocked due to another app overlaying screen
content. Since we don't know what the app is showing on top of the input target, we
can't verify user consent. [CHAR LIMIT=NONE] -->
@@ -2188,6 +2197,15 @@
<string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
<!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
+ <!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_undo">Undo</string>
+
+ <!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_undo_message_text">{count, plural,
+ =1 {{label} shortcut removed}
+ other {# shortcuts removed}
+ }</string>
+
<!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
<string name="accessibility_floating_button_action_move_top_left">Move top left</string>
<!-- Action in accessibility menu to move the accessibility floating button to the top right of the screen. [CHAR LIMIT=30] -->
@@ -2200,6 +2218,8 @@
<string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half">Move to edge and hide</string>
<!-- Action in accessibility menu to move the accessibility floating button out the edge and show. [CHAR LIMIT=36]-->
<string name="accessibility_floating_button_action_move_out_edge_and_show">Move out edge and show</string>
+ <!-- Action in accessibility menu to remove the accessibility floating menu view on the screen. [CHAR LIMIT=36]-->
+ <string name="accessibility_floating_button_action_remove_menu">Remove</string>
<!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
@@ -2527,6 +2547,12 @@
Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
<string name="mobile_data_connection_active">Connected</string>
<!-- Provider Model:
+ Summary indicating that a SIM is temporarily connected to mobile data [CHAR LIMIT=50] -->
+ <string name="mobile_data_temp_connection_active">Temporarily connected</string>
+ <!-- Provider Model:
+ Summary indicating that a SIM is temporarily connected to mobile data [CHAR LIMIT=50] -->
+ <string name="mobile_data_poor_connection">Poor connection</string>
+ <!-- Provider Model:
Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] -->
<string name="mobile_data_off_summary">Mobile data won\u0027t auto\u2011connect</string>
<!-- Provider Model:
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index cd272635905b..48821e8d0bd3 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -18,7 +18,6 @@ import android.database.ContentObserver
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Handler
-import android.os.UserHandle
import android.provider.Settings
import android.util.Log
import com.android.internal.annotations.Keep
@@ -39,15 +38,15 @@ open class ClockRegistry(
val context: Context,
val pluginManager: PluginManager,
val handler: Handler,
- defaultClockProvider: ClockProvider
+ val isEnabled: Boolean,
+ userHandle: Int,
+ defaultClockProvider: ClockProvider,
) {
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
fun onClockChanged()
}
- var isEnabled: Boolean = false
-
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
@@ -97,14 +96,19 @@ open class ClockRegistry(
)
}
- pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java,
- true /* allowMultiple */)
- context.contentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
- false,
- settingObserver,
- UserHandle.USER_ALL
- )
+ if (isEnabled) {
+ pluginManager.addPluginListener(
+ pluginListener,
+ ClockProviderPlugin::class.java,
+ /*allowMultiple=*/ true
+ )
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ /*notifyForDescendants=*/ false,
+ settingObserver,
+ userHandle
+ )
+ }
}
private fun connectClocks(provider: ClockProvider) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 7e42e1b89b1f..8ac1de898be8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -85,13 +85,12 @@ public class PipSurfaceTransactionHelper {
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
- // Scale by the shortest edge and offset such that the top/left of the scaled inset
- // source rect aligns with the top/left of the destination bounds
+ // Scale to the bounds no smaller than the destination and offset such that the top/left
+ // of the scaled inset source rect aligns with the top/left of the destination bounds
final float scale;
if (sourceRectHint.isEmpty() || sourceRectHint.width() == sourceBounds.width()) {
- scale = sourceBounds.width() <= sourceBounds.height()
- ? (float) destinationBounds.width() / sourceBounds.width()
- : (float) destinationBounds.height() / sourceBounds.height();
+ scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
+ (float) destinationBounds.height() / sourceBounds.height());
} else {
// scale by sourceRectHint if it's not edge-to-edge
final float endScale = sourceRectHint.width() <= sourceRectHint.height()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 8d1768c41589..e1e806319ba0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -228,7 +229,8 @@ public class RemoteAnimationTargetCompat {
public static RemoteAnimationTarget[] wrapNonApps(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
return wrap(info, t, leashMap, (change, taskInfo) -> (taskInfo == null)
- && wallpapers == ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0));
+ && wallpapers == change.hasFlags(FLAG_IS_WALLPAPER)
+ && !change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY));
}
private static RemoteAnimationTarget[] wrap(TransitionInfo info,
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index bb3df8f0358a..7b216017df7d 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,17 +18,15 @@ package com.android.systemui.flags
import android.content.Context
import android.os.Handler
-import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
import com.android.systemui.util.settings.SettingsUtilModule
import dagger.Binds
import dagger.Module
import dagger.Provides
-import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class,
SettingsUtilModule::class,
])
@@ -43,20 +41,5 @@ abstract class FlagsModule {
fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
return FlagManager(context, handler)
}
-
- @JvmStatic
- @Provides
- @Named(ALL_FLAGS)
- fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 0f7e732fceb1..aef887667527 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,29 +16,15 @@
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Binds
import dagger.Module
-import dagger.Provides
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
+ FlagsCommonModule::class,
ServerFlagReaderModule::class
])
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
-
- @Module
- companion object {
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object: Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 386c09575a1a..40a96b060bc0 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -30,6 +30,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.DOZING_MIGRATION_1
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -221,8 +222,11 @@ open class ClockEventController @Inject constructor(
disposableHandle = parent.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForDozing(this)
- listenForDozeAmount(this)
- listenForDozeAmountTransition(this)
+ if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ listenForDozeAmountTransition(this)
+ } else {
+ listenForDozeAmount(this)
+ }
}
}
}
@@ -265,10 +269,9 @@ open class ClockEventController @Inject constructor(
@VisibleForTesting
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.aodToLockscreenTransition.collect {
- // Would eventually run this:
- // dozeAmount = it.value
- // clock?.animations?.doze(dozeAmount)
+ keyguardTransitionInteractor.dozeAmountTransition.collect {
+ dozeAmount = it.value
+ clock?.animations?.doze(dozeAmount)
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 6fcb6f55dc20..4a41b3fe2589 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -17,6 +17,7 @@
package com.android.keyguard
import android.annotation.StringDef
+import android.os.PowerManager
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
@@ -122,122 +123,93 @@ private object InternalFaceAuthReasons {
"Face auth started/stopped because biometric is enabled on keyguard"
}
-/** UiEvents that are logged to identify why face auth is being triggered. */
-enum class FaceAuthUiEvent constructor(private val id: Int, val reason: String) :
+/**
+ * UiEvents that are logged to identify why face auth is being triggered.
+ * @param extraInfo is logged as the position. See [UiEventLogger#logWithInstanceIdAndPosition]
+ */
+enum class FaceAuthUiEvent
+constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
UiEventLogger.UiEventEnum {
@UiEvent(doc = OCCLUDING_APP_REQUESTED)
FACE_AUTH_TRIGGERED_OCCLUDING_APP_REQUESTED(1146, OCCLUDING_APP_REQUESTED),
-
@UiEvent(doc = UDFPS_POINTER_DOWN)
FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN(1147, UDFPS_POINTER_DOWN),
-
@UiEvent(doc = SWIPE_UP_ON_BOUNCER)
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER(1148, SWIPE_UP_ON_BOUNCER),
-
@UiEvent(doc = DEVICE_WOKEN_UP_ON_REACH_GESTURE)
FACE_AUTH_TRIGGERED_ON_REACH_GESTURE_ON_AOD(1149, DEVICE_WOKEN_UP_ON_REACH_GESTURE),
-
@UiEvent(doc = FACE_LOCKOUT_RESET)
FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET(1150, FACE_LOCKOUT_RESET),
-
- @UiEvent(doc = QS_EXPANDED)
- FACE_AUTH_TRIGGERED_QS_EXPANDED(1151, QS_EXPANDED),
-
+ @UiEvent(doc = QS_EXPANDED) FACE_AUTH_TRIGGERED_QS_EXPANDED(1151, QS_EXPANDED),
@UiEvent(doc = NOTIFICATION_PANEL_CLICKED)
FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED(1152, NOTIFICATION_PANEL_CLICKED),
-
@UiEvent(doc = PICK_UP_GESTURE_TRIGGERED)
FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED(1153, PICK_UP_GESTURE_TRIGGERED),
-
@UiEvent(doc = ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
- FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN(1154,
- ALTERNATE_BIOMETRIC_BOUNCER_SHOWN),
-
+ FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN(1154, ALTERNATE_BIOMETRIC_BOUNCER_SHOWN),
@UiEvent(doc = PRIMARY_BOUNCER_SHOWN)
FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN(1155, PRIMARY_BOUNCER_SHOWN),
-
@UiEvent(doc = PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN)
FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN(
1197,
PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
),
-
@UiEvent(doc = RETRY_AFTER_HW_UNAVAILABLE)
FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE(1156, RETRY_AFTER_HW_UNAVAILABLE),
-
- @UiEvent(doc = TRUST_DISABLED)
- FACE_AUTH_TRIGGERED_TRUST_DISABLED(1158, TRUST_DISABLED),
-
- @UiEvent(doc = TRUST_ENABLED)
- FACE_AUTH_STOPPED_TRUST_ENABLED(1173, TRUST_ENABLED),
-
+ @UiEvent(doc = TRUST_DISABLED) FACE_AUTH_TRIGGERED_TRUST_DISABLED(1158, TRUST_DISABLED),
+ @UiEvent(doc = TRUST_ENABLED) FACE_AUTH_STOPPED_TRUST_ENABLED(1173, TRUST_ENABLED),
@UiEvent(doc = KEYGUARD_OCCLUSION_CHANGED)
FACE_AUTH_UPDATED_KEYGUARD_OCCLUSION_CHANGED(1159, KEYGUARD_OCCLUSION_CHANGED),
-
@UiEvent(doc = ASSISTANT_VISIBILITY_CHANGED)
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED(1160, ASSISTANT_VISIBILITY_CHANGED),
-
@UiEvent(doc = STARTED_WAKING_UP)
- FACE_AUTH_UPDATED_STARTED_WAKING_UP(1161, STARTED_WAKING_UP),
-
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP(1161, STARTED_WAKING_UP) {
+ override fun extraInfoToString(): String {
+ return PowerManager.wakeReasonToString(extraInfo)
+ }
+ },
+ @Deprecated(
+ "Not a face auth trigger.",
+ ReplaceWith(
+ "FACE_AUTH_UPDATED_STARTED_WAKING_UP, " +
+ "extraInfo=PowerManager.WAKE_REASON_DREAM_FINISHED"
+ )
+ )
@UiEvent(doc = DREAM_STOPPED)
FACE_AUTH_TRIGGERED_DREAM_STOPPED(1162, DREAM_STOPPED),
-
@UiEvent(doc = ALL_AUTHENTICATORS_REGISTERED)
FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED(1163, ALL_AUTHENTICATORS_REGISTERED),
-
@UiEvent(doc = ENROLLMENTS_CHANGED)
FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED(1164, ENROLLMENTS_CHANGED),
-
@UiEvent(doc = KEYGUARD_VISIBILITY_CHANGED)
FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED(1165, KEYGUARD_VISIBILITY_CHANGED),
-
@UiEvent(doc = FACE_CANCEL_NOT_RECEIVED)
FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED(1174, FACE_CANCEL_NOT_RECEIVED),
-
@UiEvent(doc = AUTH_REQUEST_DURING_CANCELLATION)
FACE_AUTH_TRIGGERED_DURING_CANCELLATION(1175, AUTH_REQUEST_DURING_CANCELLATION),
-
- @UiEvent(doc = DREAM_STARTED)
- FACE_AUTH_STOPPED_DREAM_STARTED(1176, DREAM_STARTED),
-
- @UiEvent(doc = FP_LOCKED_OUT)
- FACE_AUTH_STOPPED_FP_LOCKED_OUT(1177, FP_LOCKED_OUT),
-
+ @UiEvent(doc = DREAM_STARTED) FACE_AUTH_STOPPED_DREAM_STARTED(1176, DREAM_STARTED),
+ @UiEvent(doc = FP_LOCKED_OUT) FACE_AUTH_STOPPED_FP_LOCKED_OUT(1177, FP_LOCKED_OUT),
@UiEvent(doc = FACE_AUTH_STOPPED_ON_USER_INPUT)
FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER(1178, FACE_AUTH_STOPPED_ON_USER_INPUT),
-
@UiEvent(doc = KEYGUARD_GOING_AWAY)
FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY(1179, KEYGUARD_GOING_AWAY),
-
- @UiEvent(doc = CAMERA_LAUNCHED)
- FACE_AUTH_UPDATED_CAMERA_LAUNCHED(1180, CAMERA_LAUNCHED),
-
- @UiEvent(doc = FP_AUTHENTICATED)
- FACE_AUTH_UPDATED_FP_AUTHENTICATED(1181, FP_AUTHENTICATED),
-
- @UiEvent(doc = GOING_TO_SLEEP)
- FACE_AUTH_UPDATED_GOING_TO_SLEEP(1182, GOING_TO_SLEEP),
-
+ @UiEvent(doc = CAMERA_LAUNCHED) FACE_AUTH_UPDATED_CAMERA_LAUNCHED(1180, CAMERA_LAUNCHED),
+ @UiEvent(doc = FP_AUTHENTICATED) FACE_AUTH_UPDATED_FP_AUTHENTICATED(1181, FP_AUTHENTICATED),
+ @UiEvent(doc = GOING_TO_SLEEP) FACE_AUTH_UPDATED_GOING_TO_SLEEP(1182, GOING_TO_SLEEP),
@UiEvent(doc = FINISHED_GOING_TO_SLEEP)
FACE_AUTH_STOPPED_FINISHED_GOING_TO_SLEEP(1183, FINISHED_GOING_TO_SLEEP),
-
- @UiEvent(doc = KEYGUARD_INIT)
- FACE_AUTH_UPDATED_ON_KEYGUARD_INIT(1189, KEYGUARD_INIT),
-
- @UiEvent(doc = KEYGUARD_RESET)
- FACE_AUTH_UPDATED_KEYGUARD_RESET(1185, KEYGUARD_RESET),
-
- @UiEvent(doc = USER_SWITCHING)
- FACE_AUTH_UPDATED_USER_SWITCHING(1186, USER_SWITCHING),
-
+ @UiEvent(doc = KEYGUARD_INIT) FACE_AUTH_UPDATED_ON_KEYGUARD_INIT(1189, KEYGUARD_INIT),
+ @UiEvent(doc = KEYGUARD_RESET) FACE_AUTH_UPDATED_KEYGUARD_RESET(1185, KEYGUARD_RESET),
+ @UiEvent(doc = USER_SWITCHING) FACE_AUTH_UPDATED_USER_SWITCHING(1186, USER_SWITCHING),
@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);
override fun getId(): Int = this.id
+
+ /** Convert [extraInfo] to a human-readable string. By default, this is empty. */
+ open fun extraInfoToString(): String = ""
}
private val apiRequestReasonToUiEvent =
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
new file mode 100644
index 000000000000..a0c43fba4bc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.keyguard
+
+import android.content.res.Resources
+import android.os.Build
+import android.os.PowerManager
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.GlobalSettings
+import java.io.PrintWriter
+import java.util.stream.Collectors
+import javax.inject.Inject
+
+/** Determines which device wake-ups should trigger face authentication. */
+@SysUISingleton
+class FaceWakeUpTriggersConfig
+@Inject
+constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpManager: DumpManager) :
+ Dumpable {
+ private val defaultTriggerFaceAuthOnWakeUpFrom: Set<Int> =
+ resources.getIntArray(R.array.config_face_auth_wake_up_triggers).toSet()
+ private val triggerFaceAuthOnWakeUpFrom: Set<Int>
+
+ init {
+ triggerFaceAuthOnWakeUpFrom =
+ if (Build.IS_DEBUGGABLE) {
+ // Update face wake triggers via adb on debuggable builds:
+ // ie: adb shell settings put global face_wake_triggers "1\|4" &&
+ // adb shell am crash com.android.systemui
+ processStringArray(
+ globalSettings.getString("face_wake_triggers"),
+ defaultTriggerFaceAuthOnWakeUpFrom
+ )
+ } else {
+ defaultTriggerFaceAuthOnWakeUpFrom
+ }
+ dumpManager.registerDumpable(this)
+ }
+
+ fun shouldTriggerFaceAuthOnWakeUpFrom(@PowerManager.WakeReason pmWakeReason: Int): Boolean {
+ return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("FaceWakeUpTriggers:")
+ for (pmWakeReason in triggerFaceAuthOnWakeUpFrom) {
+ pw.println(" ${PowerManager.wakeReasonToString(pmWakeReason)}")
+ }
+ }
+
+ /** Convert a pipe-separated set of integers into a set of ints. */
+ private fun processStringArray(stringSetting: String?, default: Set<Int>): Set<Int> {
+ return stringSetting?.let {
+ stringSetting.split("|").stream().map(Integer::parseInt).collect(Collectors.toSet())
+ }
+ ?: default
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 8eebe30222ae..ace942de1221 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -37,8 +37,6 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
@@ -120,8 +118,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
SecureSettings secureSettings,
@Main Executor uiExecutor,
DumpManager dumpManager,
- ClockEventController clockEventController,
- FeatureFlags featureFlags) {
+ ClockEventController clockEventController) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mClockRegistry = clockRegistry;
@@ -134,7 +131,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mDumpManager = dumpManager;
mClockEventController = clockEventController;
- mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS));
mClockChangedListener = () -> {
setClock(mClockRegistry.createCurrentClock());
};
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 81305f90e2b8..0b395a8760cf 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -223,7 +223,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
- boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
mKeyguardSecurityCallback.userActivity();
if (didFaceAuthRun) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index aff9dcbc26e3..39dc609c9bb7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -44,7 +44,6 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_TRUST_ENABL
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_USER_INPUT_ON_BOUNCER;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALL_AUTHENTICATORS_REGISTERED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
-import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DREAM_STOPPED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_DURING_CANCELLATION;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED;
import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_FACE_LOCKOUT_RESET;
@@ -287,6 +286,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
};
+ private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
@@ -1615,7 +1615,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onUdfpsPointerDown(int sensorId) {
mLogger.logUdfpsPointerDown(sensorId);
- requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
/**
@@ -1807,11 +1807,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- protected void handleStartedWakingUp() {
+ protected void handleStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
Assert.isMainThread();
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_STARTED_WAKING_UP);
- requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp");
+
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
+ if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) {
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
+ updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_UPDATED_STARTED_WAKING_UP);
+ requestActiveUnlock(ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE, "wakingUp - "
+ + PowerManager.wakeReasonToString(pmWakeReason));
+ } else {
+ mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason);
+ }
+
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1863,12 +1873,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onDreamingStateChanged(mIsDreaming);
}
}
+ updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
if (mIsDreaming) {
- updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_STOPPED_DREAM_STARTED);
- } else {
- updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_DREAM_STOPPED);
}
}
@@ -1948,7 +1955,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
PackageManager packageManager,
@Nullable FaceManager faceManager,
@Nullable FingerprintManager fingerprintManager,
- @Nullable BiometricManager biometricManager) {
+ @Nullable BiometricManager biometricManager,
+ FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
mContext = context;
mSubscriptionManager = subscriptionManager;
mTelephonyListenerManager = telephonyListenerManager;
@@ -1987,6 +1995,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
R.array.config_face_acquire_device_entry_ignorelist))
.boxed()
.collect(Collectors.toSet());
+ mFaceWakeUpTriggersConfig = faceWakeUpTriggersConfig;
mHandler = new Handler(mainLooper) {
@Override
@@ -2036,7 +2045,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
break;
case MSG_STARTED_WAKING_UP:
Trace.beginSection("KeyguardUpdateMonitor#handler MSG_STARTED_WAKING_UP");
- handleStartedWakingUp();
+ handleStartedWakingUp(msg.arg1);
Trace.endSection();
break;
case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
@@ -2227,8 +2236,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void updateFaceEnrolled(int userId) {
mIsFaceEnrolled = whitelistIpcs(
() -> mFaceManager != null && mFaceManager.isHardwareDetected()
- && mFaceManager.hasEnrolledTemplates(userId)
- && mBiometricEnabledForUser.get(userId));
+ && mBiometricEnabledForUser.get(userId))
+ && mAuthController.isFaceAuthEnrolled(userId);
}
public boolean isFaceSupported() {
@@ -2348,14 +2357,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* Requests face authentication if we're on a state where it's allowed.
* This will re-trigger auth in case it fails.
- * @param userInitiatedRequest true if the user explicitly requested face auth
* @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
* invoked.
* @return current face auth detection state, true if it is running.
*/
- public boolean requestFaceAuth(boolean userInitiatedRequest,
- @FaceAuthApiRequestReason String reason) {
- mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
+ public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
+ mLogger.logFaceAuthRequested(reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
return isFaceDetectionRunning();
}
@@ -2784,8 +2791,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Waiting for ERROR_CANCELED before requesting auth again
return;
}
- mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
- mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
+ mLogger.logStartedListeningForFace(mFaceRunningState, faceAuthUiEvent);
+ mUiEventLogger.logWithInstanceIdAndPosition(
+ faceAuthUiEvent,
+ 0,
+ null,
+ getKeyguardSessionId(),
+ faceAuthUiEvent.getExtraInfo()
+ );
if (unlockPossible) {
mFaceCancelSignal = new CancellationSignal();
@@ -3564,11 +3577,16 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// TODO: use these callbacks elsewhere in place of the existing notifyScreen*()
// (KeyguardViewMediator, KeyguardHostView)
- public void dispatchStartedWakingUp() {
+ /**
+ * Dispatch wakeup events to:
+ * - update biometric listening states
+ * - send to registered KeyguardUpdateMonitorCallbacks
+ */
+ public void dispatchStartedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
synchronized (this) {
mDeviceInteractive = true;
}
- mHandler.sendEmptyMessage(MSG_STARTED_WAKING_UP);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_STARTED_WAKING_UP, pmWakeReason, 0));
}
public void dispatchStartedGoingToSleep(int why) {
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 70758dfec932..8fbbd3840964 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -24,6 +24,8 @@ import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -46,6 +48,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.systemui.Dumpable;
@@ -55,6 +58,10 @@ import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -67,6 +74,7 @@ import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.PrintWriter;
import java.util.Objects;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -103,6 +111,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull private CharSequence mLockedLabel;
@NonNull private final VibratorHelper mVibrator;
@Nullable private final AuthRippleController mAuthRippleController;
+ @NonNull private final FeatureFlags mFeatureFlags;
+ @NonNull private final KeyguardTransitionInteractor mTransitionInteractor;
+ @NonNull private final KeyguardInteractor mKeyguardInteractor;
// Tracks the velocity of a touch to help filter out the touches that move too fast.
private VelocityTracker mVelocityTracker;
@@ -139,6 +150,20 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private boolean mDownDetected;
private final Rect mSensorTouchLocation = new Rect();
+ @VisibleForTesting
+ final Consumer<TransitionStep> mDozeTransitionCallback = (TransitionStep step) -> {
+ mInterpolatedDarkAmount = step.getValue();
+ mView.setDozeAmount(step.getValue());
+ updateBurnInOffsets();
+ };
+
+ @VisibleForTesting
+ final Consumer<Boolean> mIsDozingCallback = (Boolean isDozing) -> {
+ mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateVisibility();
+ };
+
@Inject
public LockIconViewController(
@Nullable LockIconView view,
@@ -154,7 +179,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull @Main DelayableExecutor executor,
@NonNull VibratorHelper vibrator,
@Nullable AuthRippleController authRippleController,
- @NonNull @Main Resources resources
+ @NonNull @Main Resources resources,
+ @NonNull KeyguardTransitionInteractor transitionInteractor,
+ @NonNull KeyguardInteractor keyguardInteractor,
+ @NonNull FeatureFlags featureFlags
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -168,6 +196,9 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mExecutor = executor;
mVibrator = vibrator;
mAuthRippleController = authRippleController;
+ mTransitionInteractor = transitionInteractor;
+ mKeyguardInteractor = keyguardInteractor;
+ mFeatureFlags = featureFlags;
mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
@@ -184,6 +215,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override
protected void onInit() {
mView.setAccessibilityDelegate(mAccessibilityDelegate);
+
+ if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ collectFlow(mView, mTransitionInteractor.getDozeAmountTransition(),
+ mDozeTransitionCallback);
+ collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback);
+ }
}
@Override
@@ -379,14 +416,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon);
- pw.println(" mIsDozing: " + mIsDozing);
- pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
- pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
- pw.println(" mRunningFPS: " + mRunningFPS);
- pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
- pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
- pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
- pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
+ pw.println();
+ pw.println(" mIsDozing: " + mIsDozing);
+ pw.println(" isFlagEnabled(DOZING_MIGRATION_1): "
+ + mFeatureFlags.isEnabled(DOZING_MIGRATION_1));
+ pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
+ pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
+ pw.println(" mRunningFPS: " + mRunningFPS);
+ pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
+ pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState));
+ pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
+ pw.println(" mSensorTouchLocation: " + mSensorTouchLocation);
if (mView != null) {
mView.dump(pw, args);
@@ -427,16 +467,20 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
- mInterpolatedDarkAmount = eased;
- mView.setDozeAmount(eased);
- updateBurnInOffsets();
+ if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ mInterpolatedDarkAmount = eased;
+ mView.setDozeAmount(eased);
+ updateBurnInOffsets();
+ }
}
@Override
public void onDozingChanged(boolean isDozing) {
- mIsDozing = isDozing;
- updateBurnInOffsets();
- updateVisibility();
+ if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) {
+ mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateVisibility();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index f43f559b4234..9767313331d3 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -18,10 +18,13 @@ package com.android.keyguard.dagger;
import android.content.Context;
import android.os.Handler;
+import android.os.UserHandle;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.shared.clocks.DefaultClockProvider;
import com.android.systemui.shared.plugins.PluginManager;
@@ -39,7 +42,14 @@ public abstract class ClockRegistryModule {
@Application Context context,
PluginManager pluginManager,
@Main Handler handler,
- DefaultClockProvider defaultClockProvider) {
- return new ClockRegistry(context, pluginManager, handler, defaultClockProvider);
+ DefaultClockProvider defaultClockProvider,
+ FeatureFlags featureFlags) {
+ return new ClockRegistry(
+ context,
+ pluginManager,
+ handler,
+ featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
+ UserHandle.USER_ALL,
+ defaultClockProvider);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 2f79e30a0b5b..3308f5550bfc 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -17,9 +17,12 @@
package com.android.keyguard.logging
import android.hardware.biometrics.BiometricConstants.LockoutMode
+import android.os.PowerManager
+import android.os.PowerManager.WakeReason
import android.telephony.ServiceState
import android.telephony.SubscriptionInfo
import com.android.keyguard.ActiveUnlockConfig
+import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.plugins.log.LogBuffer
@@ -108,11 +111,10 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, { "Face help received, msgId: $int1 msg: $str1" })
}
- fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String?) {
+ fun logFaceAuthRequested(reason: String?) {
logBuffer.log(TAG, DEBUG, {
- bool1 = userInitiatedRequest
str1 = reason
- }, { "requestFaceAuth() userInitiated=$bool1 reason=$str1" })
+ }, { "requestFaceAuth() reason=$str1" })
}
fun logFaceAuthSuccess(userId: Int) {
@@ -269,11 +271,19 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
logBuffer.log(TAG, VERBOSE, { int1 = subId }, { "reportSimUnlocked(subId=$int1)" })
}
- fun logStartedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
+ fun logStartedListeningForFace(faceRunningState: Int, faceAuthUiEvent: FaceAuthUiEvent) {
logBuffer.log(TAG, VERBOSE, {
int1 = faceRunningState
- str1 = faceAuthReason
- }, { "startListeningForFace(): $int1, reason: $str1" })
+ str1 = faceAuthUiEvent.reason
+ str2 = faceAuthUiEvent.extraInfoToString()
+ }, { "startListeningForFace(): $int1, reason: $str1 $str2" })
+ }
+
+ fun logStartedListeningForFaceFromWakeUp(faceRunningState: Int, @WakeReason pmWakeReason: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ int1 = faceRunningState
+ str1 = PowerManager.wakeReasonToString(pmWakeReason)
+ }, { "startListeningForFace(): $int1, reason: wakeUp-$str1" })
}
fun logStoppedListeningForFace(faceRunningState: Int, faceAuthReason: String) {
@@ -383,4 +393,10 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, { "#update secure=$bool1 canDismissKeyguard=$bool2" +
" trusted=$bool3 trustManaged=$bool4" })
}
+
+ fun logSkipUpdateFaceListeningOnWakeup(@WakeReason pmWakeReason: Int) {
+ logBuffer.log(TAG, VERBOSE, {
+ str1 = PowerManager.wakeReasonToString(pmWakeReason)
+ }, { "Skip updating face listening state on wakeup from $str1"})
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index ea334b27fa09..777d10c7acfd 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -28,6 +28,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.view.Display;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.MainThread;
@@ -56,6 +57,7 @@ public class AccessibilityFloatingMenuController implements
private Context mContext;
private final WindowManager mWindowManager;
private final DisplayManager mDisplayManager;
+ private final AccessibilityManager mAccessibilityManager;
private final FeatureFlags mFeatureFlags;
@VisibleForTesting
IAccessibilityFloatingMenu mFloatingMenu;
@@ -96,6 +98,7 @@ public class AccessibilityFloatingMenuController implements
public AccessibilityFloatingMenuController(Context context,
WindowManager windowManager,
DisplayManager displayManager,
+ AccessibilityManager accessibilityManager,
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -103,6 +106,7 @@ public class AccessibilityFloatingMenuController implements
mContext = context;
mWindowManager = windowManager;
mDisplayManager = displayManager;
+ mAccessibilityManager = accessibilityManager;
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -180,7 +184,8 @@ public class AccessibilityFloatingMenuController implements
final Display defaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
mFloatingMenu = new MenuViewLayerController(
mContext.createWindowContext(defaultDisplay,
- TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager);
+ TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager,
+ mAccessibilityManager);
} else {
mFloatingMenu = new AccessibilityFloatingMenu(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
new file mode 100644
index 000000000000..ee048e1a02d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationController.java
@@ -0,0 +1,179 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.ComponentCallbacks;
+import android.content.res.Configuration;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+
+import com.android.systemui.R;
+import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
+/**
+ * Controls the interaction between {@link MagnetizedObject} and
+ * {@link MagnetizedObject.MagneticTarget}.
+ */
+class DismissAnimationController implements ComponentCallbacks {
+ private static final float COMPLETELY_OPAQUE = 1.0f;
+ private static final float COMPLETELY_TRANSPARENT = 0.0f;
+ private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
+ private static final float ANIMATING_MAX_ALPHA = 0.7f;
+
+ private final DismissView mDismissView;
+ private final MenuView mMenuView;
+ private final ValueAnimator mDismissAnimator;
+ private final MagnetizedObject<?> mMagnetizedObject;
+ private float mMinDismissSize;
+ private float mSizePercent;
+
+ DismissAnimationController(DismissView dismissView, MenuView menuView) {
+ mDismissView = dismissView;
+ mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
+ mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
+ mMenuView = menuView;
+
+ updateResources();
+
+ mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+ mDismissAnimator.addUpdateListener(dismissAnimation -> {
+ final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+ final float scaleValue = Math.max(animatedValue, mSizePercent);
+ dismissView.getCircle().setScaleX(scaleValue);
+ dismissView.getCircle().setScaleY(scaleValue);
+
+ menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ });
+
+ mDismissAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ super.onAnimationEnd(animation, isReverse);
+
+ if (isReverse) {
+ mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+ mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+ mMenuView.setAlpha(COMPLETELY_OPAQUE);
+ }
+ }
+ });
+
+ mMagnetizedObject =
+ new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView,
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_X),
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_Y)) {
+ @Override
+ public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+
+ @Override
+ public float getHeight(MenuView underlyingObject) {
+ return underlyingObject.getHeight();
+ }
+
+ @Override
+ public float getWidth(MenuView underlyingObject) {
+ return underlyingObject.getWidth();
+ }
+ };
+
+ final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
+ dismissView.getCircle(), (int) mMinDismissSize);
+ mMagnetizedObject.addTarget(magneticTarget);
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ updateResources();
+ }
+
+ @Override
+ public void onLowMemory() {
+ // Do nothing
+ }
+
+ void showDismissView(boolean show) {
+ if (show) {
+ mDismissView.show();
+ } else {
+ mDismissView.hide();
+ }
+ }
+
+ void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
+ mMagnetizedObject.setMagnetListener(magnetListener);
+ }
+
+ void maybeConsumeDownMotionEvent(MotionEvent event) {
+ mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
+ * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ *
+ * @param event that move the magnetized object which is also the menu list view.
+ * @return true if the location of the motion events moves within the magnetic field of a
+ * target, but false if didn't set
+ * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ */
+ boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
+ return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ /**
+ * This used to pass {@link MotionEvent#ACTION_UP} to the magnetized object to check if it was
+ * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ *
+ * @param event that move the magnetized object which is also the menu list view.
+ * @return true if the location of the motion events moves within the magnetic field of a
+ * target, but false if didn't set
+ * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
+ */
+ boolean maybeConsumeUpMotionEvent(MotionEvent event) {
+ return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ }
+
+ void animateDismissMenu(boolean scaleUp) {
+ if (scaleUp) {
+ mDismissAnimator.start();
+ } else {
+ mDismissAnimator.reverse();
+ }
+ }
+
+ private void updateResources() {
+ final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_size);
+ mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ R.dimen.dismiss_circle_small);
+ mSizePercent = mMinDismissSize / maxDismissSize;
+ }
+
+ interface DismissCallback {
+ void onDismiss();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index d6d039903505..396f584d76a6 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -35,6 +35,8 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.util.Preconditions;
+
import java.util.HashMap;
/**
@@ -47,6 +49,9 @@ class MenuAnimationController {
private static final float MIN_PERCENT = 0.0f;
private static final float MAX_PERCENT = 1.0f;
private static final float COMPLETELY_OPAQUE = 1.0f;
+ private static final float COMPLETELY_TRANSPARENT = 0.0f;
+ private static final float SCALE_SHRINK = 0.0f;
+ private static final float SCALE_GROW = 1.0f;
private static final float FLING_FRICTION_SCALAR = 1.9f;
private static final float DEFAULT_FRICTION = 4.2f;
private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
@@ -61,6 +66,7 @@ class MenuAnimationController {
private final Handler mHandler;
private boolean mIsMovedToEdge;
private boolean mIsFadeEffectEnabled;
+ private DismissAnimationController.DismissCallback mDismissCallback;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
// DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
@@ -99,6 +105,11 @@ class MenuAnimationController {
}
}
+ void setDismissCallback(
+ DismissAnimationController.DismissCallback dismissCallback) {
+ mDismissCallback = dismissCallback;
+ }
+
void moveToTopLeftPosition() {
mIsMovedToEdge = false;
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -129,6 +140,13 @@ class MenuAnimationController {
constrainPositionAndUpdate(position);
}
+ void removeMenu() {
+ Preconditions.checkArgument(mDismissCallback != null,
+ "The dismiss callback should be initialized first.");
+
+ mDismissCallback.onDismiss();
+ }
+
void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
final boolean shouldMenuFlingLeft = isOnLeftSide()
? velocityX < ESCAPE_VELOCITY
@@ -297,6 +315,28 @@ class MenuAnimationController {
mMenuView.onDraggingStart();
}
+ void startShrinkAnimation(Runnable endAction) {
+ mMenuView.animate().cancel();
+
+ mMenuView.animate()
+ .scaleX(SCALE_SHRINK)
+ .scaleY(SCALE_SHRINK)
+ .alpha(COMPLETELY_TRANSPARENT)
+ .translationY(mMenuView.getTranslationY())
+ .withEndAction(endAction).start();
+ }
+
+ void startGrowAnimation() {
+ mMenuView.animate().cancel();
+
+ mMenuView.animate()
+ .scaleX(SCALE_GROW)
+ .scaleY(SCALE_GROW)
+ .alpha(COMPLETELY_OPAQUE)
+ .translationY(mMenuView.getTranslationY())
+ .start();
+ }
+
private void onSpringAnimationEnd(PointF position) {
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
constrainPositionAndUpdate(position);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index e69a24810fdc..ac5736b0c26d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -84,6 +84,12 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It
new AccessibilityNodeInfoCompat.AccessibilityActionCompat(moveEdgeId,
res.getString(moveEdgeTextResId));
info.addAction(moveToOrOutEdge);
+
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat removeMenu =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_remove_menu,
+ res.getString(R.string.accessibility_floating_button_action_remove_menu));
+ info.addAction(removeMenu);
}
@Override
@@ -126,6 +132,11 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It
return true;
}
+ if (action == R.id.action_remove_menu) {
+ mAnimationController.removeMenu();
+ return true;
+ }
+
return super.performAccessibilityAction(host, action, args);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 3146c9f0d2af..bc3cf0a6bab0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -38,9 +38,12 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
private final PointF mMenuTranslationDown = new PointF();
private boolean mIsDragging = false;
private float mTouchSlop;
+ private final DismissAnimationController mDismissAnimationController;
- MenuListViewTouchHandler(MenuAnimationController menuAnimationController) {
+ MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
+ DismissAnimationController dismissAnimationController) {
mMenuAnimationController = menuAnimationController;
+ mDismissAnimationController = dismissAnimationController;
}
@Override
@@ -61,6 +64,7 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
mMenuAnimationController.cancelAnimations();
+ mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) {
@@ -69,8 +73,13 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuAnimationController.onDraggingStart();
}
- mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
- mMenuAnimationController.moveToPositionYIfNeeded(mMenuTranslationDown.y + dy);
+ mDismissAnimationController.showDismissView(/* show= */ true);
+
+ if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
+ mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
+ mMenuAnimationController.moveToPositionYIfNeeded(
+ mMenuTranslationDown.y + dy);
+ }
}
break;
case MotionEvent.ACTION_UP:
@@ -79,10 +88,18 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
final float endX = mMenuTranslationDown.x + dx;
mIsDragging = false;
- if (!mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
+ mDismissAnimationController.showDismissView(/* show= */ false);
+ mMenuAnimationController.fadeOutIfEnabled();
+
+ return true;
+ }
+
+ if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+ mDismissAnimationController.showDismissView(/* show= */ false);
}
// Avoid triggering the listener of the item.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
new file mode 100644
index 000000000000..9875ad06f1ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuMessageView.java
@@ -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.
+ */
+
+package com.android.systemui.accessibility.floatingmenu;
+
+import static android.util.TypedValue.COMPLEX_UNIT_PX;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The message view with the action prompt to whether to undo operation for users when removing
+ * the {@link MenuView}.
+ */
+class MenuMessageView extends LinearLayout implements
+ ViewTreeObserver.OnComputeInternalInsetsListener {
+ private final TextView mTextView;
+ private final Button mUndoButton;
+
+ @IntDef({
+ Index.TEXT_VIEW,
+ Index.UNDO_BUTTON
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Index {
+ int TEXT_VIEW = 0;
+ int UNDO_BUTTON = 1;
+ }
+
+ MenuMessageView(Context context) {
+ super(context);
+
+ setVisibility(GONE);
+
+ mTextView = new TextView(context);
+ mUndoButton = new Button(context);
+
+ addView(mTextView, Index.TEXT_VIEW,
+ new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
+ addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateResources();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ final FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(WRAP_CONTENT,
+ WRAP_CONTENT);
+ containerParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
+ setLayoutParams(containerParams);
+ setGravity(Gravity.CENTER_VERTICAL);
+
+ mUndoButton.setBackground(null);
+
+ updateResources();
+
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+ if (getVisibility() == VISIBLE) {
+ final int x = (int) getX();
+ final int y = (int) getY();
+ inoutInfo.touchableRegion.union(new Rect(x, y, x + getWidth(), y + getHeight()));
+ }
+ }
+
+ /**
+ * Registers a listener to be invoked when this undo action button is clicked. It should be
+ * called after {@link View#onAttachedToWindow()}.
+ *
+ * @param listener The listener that will run
+ */
+ void setUndoListener(OnClickListener listener) {
+ mUndoButton.setOnClickListener(listener);
+ }
+
+ private void updateResources() {
+ final Resources res = getResources();
+
+ final int containerPadding =
+ res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_container_horizontal_padding);
+ final int margin = res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_margin);
+ final FrameLayout.LayoutParams containerParams =
+ (FrameLayout.LayoutParams) getLayoutParams();
+ containerParams.setMargins(margin, margin, margin, margin);
+ setLayoutParams(containerParams);
+ setBackground(res.getDrawable(R.drawable.accessibility_floating_message_background));
+ setPadding(containerPadding, /* top= */ 0, containerPadding, /* bottom= */ 0);
+ setMinimumWidth(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_width));
+ setMinimumHeight(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_height));
+ setElevation(
+ res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_elevation));
+
+ final int textPadding =
+ res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_text_vertical_padding);
+ final int textColor = res.getColor(R.color.accessibility_floating_menu_message_text);
+ final int textSize = res.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_message_text_size);
+ mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
+ mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
+ mTextView.setTextColor(textColor);
+
+ final ColorStateList colorAccent = Utils.getColorAccent(getContext());
+ mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
+ mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize);
+ mUndoButton.setTextColor(colorAccent);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 15d139cf15da..6a14af52fbaf 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -42,7 +42,7 @@ import java.util.Collections;
import java.util.List;
/**
- * The menu view displays the accessibility features.
+ * The container view displays the accessibility features.
*/
@SuppressLint("ViewConstructor")
class MenuView extends FrameLayout implements
@@ -64,13 +64,14 @@ class MenuView extends FrameLayout implements
this::onTargetFeaturesChanged;
private final MenuViewAppearance mMenuViewAppearance;
+ private OnTargetFeaturesChangeListener mFeaturesChangeListener;
+
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
super(context);
mMenuViewModel = menuViewModel;
mMenuViewAppearance = menuViewAppearance;
mMenuAnimationController = new MenuAnimationController(this);
-
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
@@ -96,7 +97,9 @@ class MenuView extends FrameLayout implements
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(mBoundsInParent);
+ if (getVisibility() == VISIBLE) {
+ inoutInfo.touchableRegion.union(mBoundsInParent);
+ }
}
@Override
@@ -108,10 +111,18 @@ class MenuView extends FrameLayout implements
mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
}
+ void setOnTargetFeaturesChangeListener(OnTargetFeaturesChangeListener listener) {
+ mFeaturesChangeListener = listener;
+ }
+
void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) {
mTargetFeaturesView.addOnItemTouchListener(listener);
}
+ MenuAnimationController getMenuAnimationController() {
+ return mMenuAnimationController;
+ }
+
@SuppressLint("NotifyDataSetChanged")
private void onItemSizeChanged() {
mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
@@ -139,7 +150,7 @@ class MenuView extends FrameLayout implements
onEdgeChanged();
}
- private void onEdgeChanged() {
+ void onEdgeChanged() {
final int[] insets = mMenuViewAppearance.getMenuInsets();
getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2],
insets[3]);
@@ -193,6 +204,9 @@ class MenuView extends FrameLayout implements
onEdgeChanged();
onPositionChanged();
+ if (mFeaturesChangeListener != null) {
+ mFeaturesChangeListener.onChange(newTargetFeatures);
+ }
mMenuAnimationController.fadeOutIfEnabled();
}
@@ -299,4 +313,17 @@ class MenuView extends FrameLayout implements
final ViewGroup parentView = (ViewGroup) getParent();
parentView.setSystemGestureExclusionRects(Collections.singletonList(mBoundsInParent));
}
+
+ /**
+ * Interface definition for the {@link AccessibilityTarget} list changes.
+ */
+ interface OnTargetFeaturesChangeListener {
+ /**
+ * Called when the list of accessibility target features was updated. This will be
+ * invoked when the end of {@code onTargetFeaturesChanged}.
+ *
+ * @param newTargetFeatures the list related to the current accessibility features.
+ */
+ void onChange(List<AccessibilityTarget> newTargetFeatures);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 5252519e9faf..33e155df80e3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -16,42 +16,155 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
+import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.systemui.R;
+import com.android.wm.shell.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
- * The basic interactions with the child view {@link MenuView}.
+ * The basic interactions with the child views {@link MenuView}, {@link DismissView}, and
+ * {@link MenuMessageView}. When dragging the menu view, the dismissed view would be shown at the
+ * same time. If the menu view overlaps on the dismissed circle view and drops out, the menu
+ * message view would be shown and allowed users to undo it.
*/
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout {
+ private static final int SHOW_MESSAGE_DELAY_MS = 3000;
+
private final MenuView mMenuView;
+ private final MenuMessageView mMessageView;
+ private final DismissView mDismissView;
+ private final MenuAnimationController mMenuAnimationController;
+ private final AccessibilityManager mAccessibilityManager;
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final IAccessibilityFloatingMenu mFloatingMenu;
+ private final DismissAnimationController mDismissAnimationController;
@IntDef({
- LayerIndex.MENU_VIEW
+ LayerIndex.MENU_VIEW,
+ LayerIndex.DISMISS_VIEW,
+ LayerIndex.MESSAGE_VIEW,
})
@Retention(RetentionPolicy.SOURCE)
@interface LayerIndex {
int MENU_VIEW = 0;
+ int DISMISS_VIEW = 1;
+ int MESSAGE_VIEW = 2;
}
- MenuViewLayer(@NonNull Context context, WindowManager windowManager) {
+ @VisibleForTesting
+ final Runnable mDismissMenuAction = new Runnable() {
+ @Override
+ public void run() {
+ Settings.Secure.putString(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
+ mFloatingMenu.hide();
+ }
+ };
+
+ MenuViewLayer(@NonNull Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
super(context);
+ mAccessibilityManager = accessibilityManager;
+ mFloatingMenu = floatingMenu;
+
final MenuViewModel menuViewModel = new MenuViewModel(context);
final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
windowManager);
mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
+ mMenuAnimationController = mMenuView.getMenuAnimationController();
+ mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
+
+ mDismissView = new DismissView(context);
+ mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
+ mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velocityX, float velocityY, boolean wasFlungOut) {
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+ hideMenuAndShowMessage();
+ mDismissView.hide();
+ mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ }
+ });
+
+ final MenuListViewTouchHandler menuListViewTouchHandler = new MenuListViewTouchHandler(
+ mMenuAnimationController, mDismissAnimationController);
+ mMenuView.addOnItemTouchListenerToList(menuListViewTouchHandler);
+
+ mMessageView = new MenuMessageView(context);
+
+ mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
+ if (newTargetFeatures.size() < 1) {
+ return;
+ }
+
+ // During the undo action period, the pending action will be canceled and undo back
+ // to the previous state if users did any action related to the accessibility features.
+ if (mMessageView.getVisibility() == VISIBLE) {
+ undo();
+ }
+
+ final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
+ messageText.setText(getMessageText(newTargetFeatures));
+ });
addView(mMenuView, LayerIndex.MENU_VIEW);
+ addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ addView(mMessageView, LayerIndex.MESSAGE_VIEW);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDismissView.updateResources();
+ }
+
+ private String getMessageText(List<AccessibilityTarget> newTargetFeatures) {
+ Preconditions.checkArgument(newTargetFeatures.size() > 0,
+ "The list should at least have one feature.");
+
+ final Map<String, Object> arguments = new HashMap<>();
+ arguments.put("count", newTargetFeatures.size());
+ arguments.put("label", newTargetFeatures.get(0).getLabel());
+ return PluralsMessageFormatter.format(getResources(), arguments,
+ R.string.accessibility_floating_button_undo_message_text);
}
@Override
@@ -68,6 +181,8 @@ class MenuViewLayer extends FrameLayout {
super.onAttachedToWindow();
mMenuView.show();
+ mMessageView.setUndoListener(view -> undo());
+ mContext.registerComponentCallbacks(mDismissAnimationController);
}
@Override
@@ -75,5 +190,26 @@ class MenuViewLayer extends FrameLayout {
super.onDetachedFromWindow();
mMenuView.hide();
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ mContext.unregisterComponentCallbacks(mDismissAnimationController);
+ }
+
+ private void hideMenuAndShowMessage() {
+ final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
+ SHOW_MESSAGE_DELAY_MS,
+ AccessibilityManager.FLAG_CONTENT_TEXT
+ | AccessibilityManager.FLAG_CONTENT_CONTROLS);
+ mHandler.postDelayed(mDismissMenuAction, delayTime);
+ mMessageView.setVisibility(VISIBLE);
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
+ }
+
+ private void undo() {
+ mHandler.removeCallbacksAndMessages(/* token= */ null);
+ mMessageView.setVisibility(GONE);
+ mMenuView.onEdgeChanged();
+ mMenuView.onPositionChanged();
+ mMenuView.setVisibility(VISIBLE);
+ mMenuAnimationController.startGrowAnimation();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index d2093c200ca2..b1a64eda46ff 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.graphics.PixelFormat;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
/**
* Controls the {@link MenuViewLayer} whether to be attached to the window via the interface
@@ -32,9 +33,10 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
private final MenuViewLayer mMenuViewLayer;
private boolean mIsShowing;
- MenuViewLayerController(Context context, WindowManager windowManager) {
+ MenuViewLayerController(Context context, WindowManager windowManager,
+ AccessibilityManager accessibilityManager) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context, windowManager);
+ mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b50bfd7c24f9..f74c721bf114 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -26,6 +26,7 @@ import android.annotation.DurationMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AlertDialog;
import android.content.Context;
import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
@@ -63,6 +64,9 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.CredentialView;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -74,11 +78,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import javax.inject.Provider;
+
/**
* Top level container/controller for the BiometricPrompt UI.
*/
public class AuthContainerView extends LinearLayout
- implements AuthDialog, WakefulnessLifecycle.Observer {
+ implements AuthDialog, WakefulnessLifecycle.Observer, CredentialView.Host {
private static final String TAG = "AuthContainerView";
@@ -112,15 +118,18 @@ public class AuthContainerView extends LinearLayout
private final IBinder mWindowToken = new Binder();
private final WindowManager mWindowManager;
private final Interpolator mLinearOutSlowIn;
- private final CredentialCallback mCredentialCallback;
private final LockPatternUtils mLockPatternUtils;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final InteractionJankMonitor mInteractionJankMonitor;
+ // TODO: these should be migrated out once ready
+ private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+ private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+
@VisibleForTesting final BiometricCallback mBiometricCallback;
@Nullable private AuthBiometricView mBiometricView;
- @Nullable private AuthCredentialView mCredentialView;
+ @Nullable private View mCredentialView;
private final AuthPanelController mPanelController;
private final FrameLayout mFrameLayout;
private final ImageView mBackgroundView;
@@ -229,11 +238,13 @@ public class AuthContainerView extends LinearLayout
@NonNull WakefulnessLifecycle wakefulnessLifecycle,
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
- @NonNull InteractionJankMonitor jankMonitor) {
+ @NonNull InteractionJankMonitor jankMonitor,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
mConfig.mSensorIds = sensorIds;
return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, jankMonitor, new Handler(Looper.getMainLooper()),
- bgExecutor);
+ userManager, lockPatternUtils, jankMonitor, biometricPromptInteractor,
+ credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
}
}
@@ -271,12 +282,49 @@ public class AuthContainerView extends LinearLayout
}
}
- final class CredentialCallback implements AuthCredentialView.Callback {
- @Override
- public void onCredentialMatched(byte[] attestation) {
- mCredentialAttestation = attestation;
- animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
- }
+ @Override
+ public void onCredentialMatched(@NonNull byte[] attestation) {
+ mCredentialAttestation = attestation;
+ animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
+ }
+
+ @Override
+ public void onCredentialAborted() {
+ sendEarlyUserCanceled();
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ }
+
+ @Override
+ public void onCredentialAttemptsRemaining(int remaining, @NonNull String messageBody) {
+ // Only show dialog if <=1 attempts are left before wiping.
+ if (remaining == 1) {
+ showLastAttemptBeforeWipeDialog(messageBody);
+ } else if (remaining <= 0) {
+ showNowWipingDialog(messageBody);
+ }
+ }
+
+ private void showLastAttemptBeforeWipeDialog(@NonNull String messageBody) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
+ .setMessage(messageBody)
+ .setPositiveButton(android.R.string.ok, null)
+ .create();
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.show();
+ }
+
+ private void showNowWipingDialog(@NonNull String messageBody) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
+ .setMessage(messageBody)
+ .setPositiveButton(
+ com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
+ null /* OnClickListener */)
+ .setOnDismissListener(
+ dialog -> animateAway(AuthDialogCallback.DISMISSED_ERROR))
+ .create();
+ alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ alertDialog.show();
}
@VisibleForTesting
@@ -287,6 +335,8 @@ public class AuthContainerView extends LinearLayout
@NonNull UserManager userManager,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
@NonNull @Background DelayableExecutor bgExecutor) {
super(config.mContext);
@@ -302,7 +352,6 @@ public class AuthContainerView extends LinearLayout
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
mBiometricCallback = new BiometricCallback();
- mCredentialCallback = new CredentialCallback();
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
mFrameLayout = (FrameLayout) layoutInflater.inflate(
@@ -314,6 +363,8 @@ public class AuthContainerView extends LinearLayout
mPanelController = new AuthPanelController(mContext, mPanelView);
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
+ mBiometricPromptInteractor = biometricPromptInteractor;
+ mCredentialViewModelProvider = credentialViewModelProvider;
// Inflate biometric view only if necessary.
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
@@ -404,12 +455,12 @@ public class AuthContainerView extends LinearLayout
switch (credentialType) {
case Utils.CREDENTIAL_PATTERN:
- mCredentialView = (AuthCredentialView) factory.inflate(
+ mCredentialView = factory.inflate(
R.layout.auth_credential_pattern_view, null, false);
break;
case Utils.CREDENTIAL_PIN:
case Utils.CREDENTIAL_PASSWORD:
- mCredentialView = (AuthCredentialView) factory.inflate(
+ mCredentialView = factory.inflate(
R.layout.auth_credential_password_view, null, false);
break;
default:
@@ -422,16 +473,12 @@ public class AuthContainerView extends LinearLayout
mBackgroundView.setOnClickListener(null);
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mCredentialView.setContainerView(this);
- mCredentialView.setUserId(mConfig.mUserId);
- mCredentialView.setOperationId(mConfig.mOperationId);
- mCredentialView.setEffectiveUserId(mEffectiveUserId);
- mCredentialView.setCredentialType(credentialType);
- mCredentialView.setCallback(mCredentialCallback);
- mCredentialView.setPromptInfo(mConfig.mPromptInfo);
- mCredentialView.setPanelController(mPanelController, animatePanel);
- mCredentialView.setShouldAnimateContents(animateContents);
- mCredentialView.setBackgroundExecutor(mBackgroundExecutor);
+ mBiometricPromptInteractor.get().useCredentialsForAuthentication(
+ mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+ final CredentialViewModel vm = mCredentialViewModelProvider.get();
+ vm.setAnimateContents(animateContents);
+ ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
+
mFrameLayout.addView(mCredentialView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 8c7e0efee7e6..313ff4157155 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -72,6 +72,8 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.os.SomeArgs;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.CoreStartable;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -122,6 +124,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
private final Provider<UdfpsController> mUdfpsControllerFactory;
private final Provider<SidefpsController> mSidefpsControllerFactory;
+ // TODO: these should be migrated out once ready
+ @NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
+ @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
+
private final Display mDisplay;
private float mScaleFactor = 1f;
// sensor locations without any resolution scaling nor rotation adjustments:
@@ -153,6 +159,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+ @NonNull private final SparseBooleanArray mFaceEnrolledForUser;
@NonNull private final SensorPrivacyManager mSensorPrivacyManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -349,6 +356,15 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
}
}
+ if (mFaceProps == null) {
+ Log.d(TAG, "handleEnrollmentsChanged, mFaceProps is null");
+ } else {
+ for (FaceSensorPropertiesInternal prop : mFaceProps) {
+ if (prop.sensorId == sensorId) {
+ mFaceEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
for (Callback cb : mCallbacks) {
cb.onEnrollmentsChanged(modality);
}
@@ -683,6 +699,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull LockPatternUtils lockPatternUtils,
@NonNull UdfpsLogger udfpsLogger,
@NonNull StatusBarStateController statusBarStateController,
+ @NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
+ @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
@@ -704,8 +722,12 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mWindowManager = windowManager;
mInteractionJankMonitor = jankMonitor;
mUdfpsEnrolledForUser = new SparseBooleanArray();
+ mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
+ mBiometricPromptInteractor = biometricPromptInteractor;
+ mCredentialViewModelProvider = credentialViewModelProvider;
+
mOrientationListener = new BiometricDisplayListener(
context,
mDisplayManager,
@@ -1054,7 +1076,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return false;
}
- return mFaceManager.hasEnrolledTemplates(userId);
+ return mFaceEnrolledForUser.get(userId);
}
/**
@@ -1068,6 +1090,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
return mUdfpsEnrolledForUser.get(userId);
}
+ /** If BiometricPrompt is currently being shown to the user. */
+ public boolean isShowing() {
+ return mCurrentDialog != null;
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
@@ -1199,7 +1226,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
.setMultiSensorConfig(multiSensorConfig)
.setScaleFactorProvider(() -> getScaleFactor())
.build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
- userManager, lockPatternUtils, mInteractionJankMonitor);
+ userManager, lockPatternUtils, mInteractionJankMonitor,
+ mBiometricPromptInteractor, mCredentialViewModelProvider);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
deleted file mode 100644
index 76cd3f4c4f1d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowInsets.Type.ime;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.Insets;
-import android.os.UserHandle;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.View.OnApplyWindowInsetsListener;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.ImeAwareEditText;
-import android.widget.TextView;
-
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockscreenCredential;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.Dumpable;
-import com.android.systemui.R;
-
-import java.io.PrintWriter;
-
-/**
- * Pin and Password UI
- */
-public class AuthCredentialPasswordView extends AuthCredentialView
- implements TextView.OnEditorActionListener, OnApplyWindowInsetsListener, Dumpable {
-
- private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView";
-
- private final InputMethodManager mImm;
- private ImeAwareEditText mPasswordField;
- private ViewGroup mAuthCredentialHeader;
- private ViewGroup mAuthCredentialInput;
- private int mBottomInset = 0;
-
- public AuthCredentialPasswordView(Context context,
- AttributeSet attrs) {
- super(context, attrs);
- mImm = mContext.getSystemService(InputMethodManager.class);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mAuthCredentialHeader = findViewById(R.id.auth_credential_header);
- mAuthCredentialInput = findViewById(R.id.auth_credential_input);
- mPasswordField = findViewById(R.id.lockPassword);
- mPasswordField.setOnEditorActionListener(this);
- // TODO: De-dupe the logic with AuthContainerView
- mPasswordField.setOnKeyListener((v, keyCode, event) -> {
- if (keyCode != KeyEvent.KEYCODE_BACK) {
- return false;
- }
- if (event.getAction() == KeyEvent.ACTION_UP) {
- mContainerView.sendEarlyUserCanceled();
- mContainerView.animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
- }
- return true;
- });
-
- setOnApplyWindowInsetsListener(this);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- mPasswordField.setTextOperationUser(UserHandle.of(mUserId));
- if (mCredentialType == Utils.CREDENTIAL_PIN) {
- mPasswordField.setInputType(
- InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
- }
-
- mPasswordField.requestFocus();
- mPasswordField.scheduleShowSoftInput();
- }
-
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // Check if this was the result of hitting the enter key
- final boolean isSoftImeEvent = event == null
- && (actionId == EditorInfo.IME_NULL
- || actionId == EditorInfo.IME_ACTION_DONE
- || actionId == EditorInfo.IME_ACTION_NEXT);
- final boolean isKeyboardEnterKey = event != null
- && KeyEvent.isConfirmKey(event.getKeyCode())
- && event.getAction() == KeyEvent.ACTION_DOWN;
- if (isSoftImeEvent || isKeyboardEnterKey) {
- checkPasswordAndUnlock();
- return true;
- }
- return false;
- }
-
- private void checkPasswordAndUnlock() {
- try (LockscreenCredential password = mCredentialType == Utils.CREDENTIAL_PIN
- ? LockscreenCredential.createPinOrNone(mPasswordField.getText())
- : LockscreenCredential.createPasswordOrNone(mPasswordField.getText())) {
- if (password.isNone()) {
- return;
- }
-
- // Request LockSettingsService to return the Gatekeeper Password in the
- // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
- // Gatekeeper Password and operationId.
- mPendingLockCheck = LockPatternChecker.verifyCredential(mLockPatternUtils,
- password, mEffectiveUserId, LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
- this::onCredentialVerified);
- }
- }
-
- @Override
- protected void onCredentialVerified(@NonNull VerifyCredentialResponse response,
- int timeoutMs) {
- super.onCredentialVerified(response, timeoutMs);
-
- if (response.isMatched()) {
- mImm.hideSoftInputFromWindow(getWindowToken(), 0 /* flags */);
- } else {
- mPasswordField.setText("");
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (mAuthCredentialInput == null || mAuthCredentialHeader == null || mSubtitleView == null
- || mDescriptionView == null || mPasswordField == null || mErrorView == null) {
- return;
- }
-
- int inputLeftBound;
- int inputTopBound;
- int headerRightBound = right;
- int headerTopBounds = top;
- final int subTitleBottom = (mSubtitleView.getVisibility() == GONE) ? mTitleView.getBottom()
- : mSubtitleView.getBottom();
- final int descBottom = (mDescriptionView.getVisibility() == GONE) ? subTitleBottom
- : mDescriptionView.getBottom();
- if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- inputTopBound = (bottom - mAuthCredentialInput.getHeight()) / 2;
- inputLeftBound = (right - left) / 2;
- headerRightBound = inputLeftBound;
- headerTopBounds -= Math.min(mIconView.getBottom(), mBottomInset);
- } else {
- inputTopBound =
- descBottom + (bottom - descBottom - mAuthCredentialInput.getHeight()) / 2;
- inputLeftBound = (right - left - mAuthCredentialInput.getWidth()) / 2;
- }
-
- if (mDescriptionView.getBottom() > mBottomInset) {
- mAuthCredentialHeader.layout(left, headerTopBounds, headerRightBound, bottom);
- }
- mAuthCredentialInput.layout(inputLeftBound, inputTopBound, right, bottom);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- final int newWidth = MeasureSpec.getSize(widthMeasureSpec);
- final int newHeight = MeasureSpec.getSize(heightMeasureSpec) - mBottomInset;
-
- setMeasuredDimension(newWidth, newHeight);
-
- final int halfWidthSpec = MeasureSpec.makeMeasureSpec(getWidth() / 2,
- MeasureSpec.AT_MOST);
- final int fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED);
- if (getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- measureChildren(halfWidthSpec, fullHeightSpec);
- } else {
- measureChildren(widthMeasureSpec, fullHeightSpec);
- }
- }
-
- @NonNull
- @Override
- public WindowInsets onApplyWindowInsets(@NonNull View v, WindowInsets insets) {
-
- final Insets bottomInset = insets.getInsets(ime());
- if (v instanceof AuthCredentialPasswordView && mBottomInset != bottomInset.bottom) {
- mBottomInset = bottomInset.bottom;
- if (mBottomInset > 0
- && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE) {
- mTitleView.setSingleLine(true);
- mTitleView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
- mTitleView.setMarqueeRepeatLimit(-1);
- // select to enable marquee unless a screen reader is enabled
- mTitleView.setSelected(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
- } else {
- mTitleView.setSingleLine(false);
- mTitleView.setEllipsize(null);
- // select to enable marquee unless a screen reader is enabled
- mTitleView.setSelected(false);
- }
- requestLayout();
- }
- return insets;
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println(TAG + "State:");
- pw.println(" mBottomInset=" + mBottomInset);
- pw.println(" mAuthCredentialHeader size=(" + mAuthCredentialHeader.getWidth() + ","
- + mAuthCredentialHeader.getHeight());
- pw.println(" mAuthCredentialInput size=(" + mAuthCredentialInput.getWidth() + ","
- + mAuthCredentialInput.getHeight());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
deleted file mode 100644
index f9e44a0c1724..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.util.AttributeSet;
-
-import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.LockPatternView;
-import com.android.internal.widget.LockscreenCredential;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.R;
-
-import java.util.List;
-
-/**
- * Pattern UI
- */
-public class AuthCredentialPatternView extends AuthCredentialView {
-
- private LockPatternView mLockPatternView;
-
- private class UnlockPatternListener implements LockPatternView.OnPatternListener {
-
- @Override
- public void onPatternStart() {
-
- }
-
- @Override
- public void onPatternCleared() {
-
- }
-
- @Override
- public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
-
- }
-
- @Override
- public void onPatternDetected(List<LockPatternView.Cell> pattern) {
- if (mPendingLockCheck != null) {
- mPendingLockCheck.cancel(false);
- }
-
- mLockPatternView.setEnabled(false);
-
- if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
- // Pattern size is less than the minimum, do not count it as a failed attempt.
- onPatternVerified(VerifyCredentialResponse.ERROR, 0 /* timeoutMs */);
- return;
- }
-
- try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
- // Request LockSettingsService to return the Gatekeeper Password in the
- // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
- // Gatekeeper Password and operationId.
- mPendingLockCheck = LockPatternChecker.verifyCredential(
- mLockPatternUtils,
- credential,
- mEffectiveUserId,
- LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE,
- this::onPatternVerified);
- }
- }
-
- private void onPatternVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
- AuthCredentialPatternView.this.onCredentialVerified(response, timeoutMs);
- if (timeoutMs > 0) {
- mLockPatternView.setEnabled(false);
- } else {
- mLockPatternView.setEnabled(true);
- }
- }
- }
-
- @Override
- protected void onErrorTimeoutFinish() {
- super.onErrorTimeoutFinish();
- // select to enable marquee unless a screen reader is enabled
- mLockPatternView.setEnabled(!mAccessibilityManager.isEnabled()
- || !mAccessibilityManager.isTouchExplorationEnabled());
- }
-
- public AuthCredentialPatternView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLockPatternView = findViewById(R.id.lockPattern);
- mLockPatternView.setOnPatternListener(new UnlockPatternListener());
- mLockPatternView.setInStealthMode(
- !mLockPatternUtils.isVisiblePatternEnabled(mUserId));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
deleted file mode 100644
index fa623d146756..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ /dev/null
@@ -1,565 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
-import static android.app.admin.DevicePolicyResources.UNDEFINED;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AlertDialog;
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.PromptInfo;
-import android.os.AsyncTask;
-import android.os.CountDownTimer;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.StringRes;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Abstract base class for Pin, Pattern, or Password authentication, for
- * {@link BiometricPrompt.Builder#setAllowedAuthenticators(int)}}
- */
-public abstract class AuthCredentialView extends LinearLayout {
- private static final String TAG = "BiometricPrompt/AuthCredentialView";
- private static final int ERROR_DURATION_MS = 3000;
-
- static final int USER_TYPE_PRIMARY = 1;
- static final int USER_TYPE_MANAGED_PROFILE = 2;
- static final int USER_TYPE_SECONDARY = 3;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({USER_TYPE_PRIMARY, USER_TYPE_MANAGED_PROFILE, USER_TYPE_SECONDARY})
- private @interface UserType {}
-
- protected final Handler mHandler;
- protected final LockPatternUtils mLockPatternUtils;
-
- protected final AccessibilityManager mAccessibilityManager;
- private final UserManager mUserManager;
- private final DevicePolicyManager mDevicePolicyManager;
-
- private PromptInfo mPromptInfo;
- private AuthPanelController mPanelController;
- private boolean mShouldAnimatePanel;
- private boolean mShouldAnimateContents;
-
- protected TextView mTitleView;
- protected TextView mSubtitleView;
- protected TextView mDescriptionView;
- protected ImageView mIconView;
- protected TextView mErrorView;
-
- protected @Utils.CredentialType int mCredentialType;
- protected AuthContainerView mContainerView;
- protected Callback mCallback;
- protected AsyncTask<?, ?, ?> mPendingLockCheck;
- protected int mUserId;
- protected long mOperationId;
- protected int mEffectiveUserId;
- protected ErrorTimer mErrorTimer;
-
- protected @Background DelayableExecutor mBackgroundExecutor;
-
- interface Callback {
- void onCredentialMatched(byte[] attestation);
- }
-
- protected static class ErrorTimer extends CountDownTimer {
- private final TextView mErrorView;
- private final Context mContext;
-
- /**
- * @param millisInFuture The number of millis in the future from the call
- * to {@link #start()} until the countdown is done and {@link
- * #onFinish()}
- * is called.
- * @param countDownInterval The interval along the way to receive
- * {@link #onTick(long)} callbacks.
- */
- public ErrorTimer(Context context, long millisInFuture, long countDownInterval,
- TextView errorView) {
- super(millisInFuture, countDownInterval);
- mErrorView = errorView;
- mContext = context;
- }
-
- @Override
- public void onTick(long millisUntilFinished) {
- final int secondsCountdown = (int) (millisUntilFinished / 1000);
- mErrorView.setText(mContext.getString(
- R.string.biometric_dialog_credential_too_many_attempts, secondsCountdown));
- }
-
- @Override
- public void onFinish() {
- if (mErrorView != null) {
- mErrorView.setText("");
- }
- }
- }
-
- protected final Runnable mClearErrorRunnable = new Runnable() {
- @Override
- public void run() {
- if (mErrorView != null) {
- mErrorView.setText("");
- }
- }
- };
-
- public AuthCredentialView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- mLockPatternUtils = new LockPatternUtils(mContext);
- mHandler = new Handler(Looper.getMainLooper());
- mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mUserManager = mContext.getSystemService(UserManager.class);
- mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
- }
-
- protected void showError(String error) {
- if (mHandler != null) {
- mHandler.removeCallbacks(mClearErrorRunnable);
- mHandler.postDelayed(mClearErrorRunnable, ERROR_DURATION_MS);
- }
- if (mErrorView != null) {
- mErrorView.setText(error);
- }
- }
-
- private void setTextOrHide(TextView view, CharSequence text) {
- if (TextUtils.isEmpty(text)) {
- view.setVisibility(View.GONE);
- } else {
- view.setText(text);
- }
-
- Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
- }
-
- private void setText(TextView view, CharSequence text) {
- view.setText(text);
- }
-
- void setUserId(int userId) {
- mUserId = userId;
- }
-
- void setOperationId(long operationId) {
- mOperationId = operationId;
- }
-
- void setEffectiveUserId(int effectiveUserId) {
- mEffectiveUserId = effectiveUserId;
- }
-
- void setCredentialType(@Utils.CredentialType int credentialType) {
- mCredentialType = credentialType;
- }
-
- void setCallback(Callback callback) {
- mCallback = callback;
- }
-
- void setPromptInfo(PromptInfo promptInfo) {
- mPromptInfo = promptInfo;
- }
-
- void setPanelController(AuthPanelController panelController, boolean animatePanel) {
- mPanelController = panelController;
- mShouldAnimatePanel = animatePanel;
- }
-
- void setShouldAnimateContents(boolean animateContents) {
- mShouldAnimateContents = animateContents;
- }
-
- void setContainerView(AuthContainerView containerView) {
- mContainerView = containerView;
- }
-
- void setBackgroundExecutor(@Background DelayableExecutor bgExecutor) {
- mBackgroundExecutor = bgExecutor;
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- final CharSequence title = getTitle(mPromptInfo);
- setText(mTitleView, title);
- setTextOrHide(mSubtitleView, getSubtitle(mPromptInfo));
- setTextOrHide(mDescriptionView, getDescription(mPromptInfo));
- announceForAccessibility(title);
-
- if (mIconView != null) {
- final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
- final Drawable image;
- if (isManagedProfile) {
- image = getResources().getDrawable(R.drawable.auth_dialog_enterprise,
- mContext.getTheme());
- } else {
- image = getResources().getDrawable(R.drawable.auth_dialog_lock,
- mContext.getTheme());
- }
- mIconView.setImageDrawable(image);
- }
-
- // Only animate this if we're transitioning from a biometric view.
- if (mShouldAnimateContents) {
- setTranslationY(getResources()
- .getDimension(R.dimen.biometric_dialog_credential_translation_offset));
- setAlpha(0);
-
- postOnAnimation(() -> {
- animate().translationY(0)
- .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS)
- .alpha(1.f)
- .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
- .withLayer()
- .start();
- });
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mErrorTimer != null) {
- mErrorTimer.cancel();
- }
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTitleView = findViewById(R.id.title);
- mSubtitleView = findViewById(R.id.subtitle);
- mDescriptionView = findViewById(R.id.description);
- mIconView = findViewById(R.id.icon);
- mErrorView = findViewById(R.id.error);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
-
- if (mShouldAnimatePanel) {
- // Credential view is always full screen.
- mPanelController.setUseFullScreen(true);
- mPanelController.updateForContentDimensions(mPanelController.getContainerWidth(),
- mPanelController.getContainerHeight(), 0 /* animateDurationMs */);
- mShouldAnimatePanel = false;
- }
- }
-
- protected void onErrorTimeoutFinish() {}
-
- protected void onCredentialVerified(@NonNull VerifyCredentialResponse response, int timeoutMs) {
- if (response.isMatched()) {
- mClearErrorRunnable.run();
- mLockPatternUtils.userPresent(mEffectiveUserId);
-
- // The response passed into this method contains the Gatekeeper Password. We still
- // have to request Gatekeeper to create a Hardware Auth Token with the
- // Gatekeeper Password and Challenge (keystore operationId in this case)
- final long pwHandle = response.getGatekeeperPasswordHandle();
- final VerifyCredentialResponse gkResponse = mLockPatternUtils
- .verifyGatekeeperPasswordHandle(pwHandle, mOperationId, mEffectiveUserId);
-
- mCallback.onCredentialMatched(gkResponse.getGatekeeperHAT());
- mLockPatternUtils.removeGatekeeperPasswordHandle(pwHandle);
- } else {
- if (timeoutMs > 0) {
- mHandler.removeCallbacks(mClearErrorRunnable);
- long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
- mEffectiveUserId, timeoutMs);
- mErrorTimer = new ErrorTimer(mContext,
- deadline - SystemClock.elapsedRealtime(),
- LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS,
- mErrorView) {
- @Override
- public void onFinish() {
- onErrorTimeoutFinish();
- mClearErrorRunnable.run();
- }
- };
- mErrorTimer.start();
- } else {
- final boolean didUpdateErrorText = reportFailedAttempt();
- if (!didUpdateErrorText) {
- final @StringRes int errorRes;
- switch (mCredentialType) {
- case Utils.CREDENTIAL_PIN:
- errorRes = R.string.biometric_dialog_wrong_pin;
- break;
- case Utils.CREDENTIAL_PATTERN:
- errorRes = R.string.biometric_dialog_wrong_pattern;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- errorRes = R.string.biometric_dialog_wrong_password;
- break;
- }
- showError(getResources().getString(errorRes));
- }
- }
- }
- }
-
- private boolean reportFailedAttempt() {
- boolean result = updateErrorMessage(
- mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
- mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
- return result;
- }
-
- private boolean updateErrorMessage(int numAttempts) {
- // Don't show any message if there's no maximum number of attempts.
- final int maxAttempts = mLockPatternUtils.getMaximumFailedPasswordsForWipe(
- mEffectiveUserId);
- if (maxAttempts <= 0 || numAttempts <= 0) {
- return false;
- }
-
- // Update the on-screen error string.
- if (mErrorView != null) {
- final String message = getResources().getString(
- R.string.biometric_dialog_credential_attempts_before_wipe,
- numAttempts,
- maxAttempts);
- showError(message);
- }
-
- // Only show dialog if <=1 attempts are left before wiping.
- final int remainingAttempts = maxAttempts - numAttempts;
- if (remainingAttempts == 1) {
- showLastAttemptBeforeWipeDialog();
- } else if (remainingAttempts <= 0) {
- showNowWipingDialog();
- }
- return true;
- }
-
- private void showLastAttemptBeforeWipeDialog() {
- mBackgroundExecutor.execute(() -> {
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setTitle(R.string.biometric_dialog_last_attempt_before_wipe_dialog_title)
- .setMessage(
- getLastAttemptBeforeWipeMessage(getUserTypeForWipe(), mCredentialType))
- .setPositiveButton(android.R.string.ok, null)
- .create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- mHandler.post(alertDialog::show);
- });
- }
-
- private void showNowWipingDialog() {
- mBackgroundExecutor.execute(() -> {
- String nowWipingMessage = getNowWipingMessage(getUserTypeForWipe());
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setMessage(nowWipingMessage)
- .setPositiveButton(
- com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
- null /* OnClickListener */)
- .setOnDismissListener(
- dialog -> mContainerView.animateAway(
- AuthDialogCallback.DISMISSED_ERROR))
- .create();
- alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
- mHandler.post(alertDialog::show);
- });
- }
-
- private @UserType int getUserTypeForWipe() {
- final UserInfo userToBeWiped = mUserManager.getUserInfo(
- mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
- if (userToBeWiped == null || userToBeWiped.isPrimary()) {
- return USER_TYPE_PRIMARY;
- } else if (userToBeWiped.isManagedProfile()) {
- return USER_TYPE_MANAGED_PROFILE;
- } else {
- return USER_TYPE_SECONDARY;
- }
- }
-
- // This should not be called on the main thread to avoid making an IPC.
- private String getLastAttemptBeforeWipeMessage(
- @UserType int userType, @Utils.CredentialType int credentialType) {
- switch (userType) {
- case USER_TYPE_PRIMARY:
- return getLastAttemptBeforeWipeDeviceMessage(credentialType);
- case USER_TYPE_MANAGED_PROFILE:
- return getLastAttemptBeforeWipeProfileMessage(credentialType);
- case USER_TYPE_SECONDARY:
- return getLastAttemptBeforeWipeUserMessage(credentialType);
- default:
- throw new IllegalArgumentException("Unrecognized user type:" + userType);
- }
- }
-
- private String getLastAttemptBeforeWipeDeviceMessage(
- @Utils.CredentialType int credentialType) {
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- return mContext.getString(
- R.string.biometric_dialog_last_pin_attempt_before_wipe_device);
- case Utils.CREDENTIAL_PATTERN:
- return mContext.getString(
- R.string.biometric_dialog_last_pattern_attempt_before_wipe_device);
- case Utils.CREDENTIAL_PASSWORD:
- default:
- return mContext.getString(
- R.string.biometric_dialog_last_password_attempt_before_wipe_device);
- }
- }
-
- // This should not be called on the main thread to avoid making an IPC.
- private String getLastAttemptBeforeWipeProfileMessage(
- @Utils.CredentialType int credentialType) {
- return mDevicePolicyManager.getResources().getString(
- getLastAttemptBeforeWipeProfileUpdatableStringId(credentialType),
- () -> getLastAttemptBeforeWipeProfileDefaultMessage(credentialType));
- }
-
- private static String getLastAttemptBeforeWipeProfileUpdatableStringId(
- @Utils.CredentialType int credentialType) {
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- return BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT;
- case Utils.CREDENTIAL_PATTERN:
- return BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- return BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT;
- }
- }
-
- private String getLastAttemptBeforeWipeProfileDefaultMessage(
- @Utils.CredentialType int credentialType) {
- int resId;
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_profile;
- break;
- case Utils.CREDENTIAL_PATTERN:
- resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- resId = R.string.biometric_dialog_last_password_attempt_before_wipe_profile;
- }
- return mContext.getString(resId);
- }
-
- private String getLastAttemptBeforeWipeUserMessage(
- @Utils.CredentialType int credentialType) {
- int resId;
- switch (credentialType) {
- case Utils.CREDENTIAL_PIN:
- resId = R.string.biometric_dialog_last_pin_attempt_before_wipe_user;
- break;
- case Utils.CREDENTIAL_PATTERN:
- resId = R.string.biometric_dialog_last_pattern_attempt_before_wipe_user;
- break;
- case Utils.CREDENTIAL_PASSWORD:
- default:
- resId = R.string.biometric_dialog_last_password_attempt_before_wipe_user;
- }
- return mContext.getString(resId);
- }
-
- private String getNowWipingMessage(@UserType int userType) {
- return mDevicePolicyManager.getResources().getString(
- getNowWipingUpdatableStringId(userType),
- () -> getNowWipingDefaultMessage(userType));
- }
-
- private String getNowWipingUpdatableStringId(@UserType int userType) {
- switch (userType) {
- case USER_TYPE_MANAGED_PROFILE:
- return BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS;
- default:
- return UNDEFINED;
- }
- }
-
- private String getNowWipingDefaultMessage(@UserType int userType) {
- int resId;
- switch (userType) {
- case USER_TYPE_PRIMARY:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_device;
- break;
- case USER_TYPE_MANAGED_PROFILE:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_profile;
- break;
- case USER_TYPE_SECONDARY:
- resId = com.android.settingslib.R.string.failed_attempts_now_wiping_user;
- break;
- default:
- throw new IllegalArgumentException("Unrecognized user type:" + userType);
- }
- return mContext.getString(resId);
- }
-
- @Nullable
- private static CharSequence getTitle(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialTitle = promptInfo.getDeviceCredentialTitle();
- return credentialTitle != null ? credentialTitle : promptInfo.getTitle();
- }
-
- @Nullable
- private static CharSequence getSubtitle(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialSubtitle = promptInfo.getDeviceCredentialSubtitle();
- return credentialSubtitle != null ? credentialSubtitle : promptInfo.getSubtitle();
- }
-
- @Nullable
- private static CharSequence getDescription(@NonNull PromptInfo promptInfo) {
- final CharSequence credentialDescription = promptInfo.getDeviceCredentialDescription();
- return credentialDescription != null ? credentialDescription : promptInfo.getDescription();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index f1e42e0c5454..5c616f005d4d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -177,11 +177,11 @@ public class AuthPanelController extends ViewOutlineProvider {
}
}
- int getContainerWidth() {
+ public int getContainerWidth() {
return mContainerWidth;
}
- int getContainerHeight() {
+ public int getContainerHeight() {
return mContainerHeight;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3273d7429d49..48c60a0548ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -60,6 +60,8 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -119,6 +121,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final VibratorHelper mVibrator;
+ @NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -202,6 +205,10 @@ public class UdfpsController implements DozeReceiver {
@Override
public void showUdfpsOverlay(long requestId, int sensorId, int reason,
@NonNull IUdfpsOverlayControllerCallback callback) {
+ if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return;
+ }
+
mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
mWindowManager, mAccessibilityManager, mStatusBarStateController,
@@ -217,6 +224,10 @@ public class UdfpsController implements DozeReceiver {
@Override
public void hideUdfpsOverlay(int sensorId) {
+ if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return;
+ }
+
mFgExecutor.execute(() -> {
if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
// if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
@@ -590,6 +601,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+ @NonNull FeatureFlags featureFlags,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -625,6 +637,7 @@ public class UdfpsController implements DozeReceiver {
mDumpManager = dumpManager;
mDialogManager = dialogManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mFeatureFlags = featureFlags;
mFalsingManager = falsingManager;
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
@@ -859,9 +872,7 @@ public class UdfpsController implements DozeReceiver {
playStartHaptic();
if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
- mKeyguardUpdateMonitor.requestFaceAuth(
- /* userInitiatedRequest */ false,
- FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
}
mOnFingerDown = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
index 6e78f3d3d6aa..142642a2411f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -19,26 +19,40 @@ package com.android.systemui.biometrics
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
+import android.graphics.Point
import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.hardware.fingerprint.IUdfpsOverlay
import android.os.Handler
+import android.provider.Settings
import android.view.MotionEvent
-import android.view.View
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable
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.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.Execution
-import java.util.*
+import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
private const val TAG = "UdfpsOverlay"
+const val SETTING_OVERLAY_DEBUG = "udfps_overlay_debug"
+
+// Number of sensor points needed inside ellipse for good overlap
+private const val NEEDED_POINTS = 2
+
@SuppressLint("ClickableViewAccessibility")
@SysUISingleton
class UdfpsOverlay
@@ -51,10 +65,11 @@ constructor(
private val handler: Handler,
private val biometricExecutor: Executor,
private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
- private val fgExecutor: DelayableExecutor,
+ @Main private val fgExecutor: DelayableExecutor,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
- private val udfpsLogger: UdfpsLogger
+ private val udfpsLogger: UdfpsLogger,
+ private var featureFlags: FeatureFlags
) : CoreStartable {
/** The view, when [isShowing], or null. */
@@ -64,7 +79,11 @@ constructor(
private var requestId: Long = 0
private var onFingerDown = false
val size = windowManager.maximumWindowMetrics.bounds
+
val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf()
+ var points: Array<Point> = emptyArray()
+ var processedMotionEvent = false
+ var isShowing = false
private var params: UdfpsOverlayParams = UdfpsOverlayParams()
@@ -87,41 +106,57 @@ constructor(
inputFeatures = INPUT_FEATURE_SPY
}
- fun onTouch(v: View, event: MotionEvent): Boolean {
- val view = v as UdfpsOverlayView
+ fun onTouch(event: MotionEvent): Boolean {
+ val view = overlayView!!
return when (event.action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE -> {
onFingerDown = true
if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
- biometricExecutor.execute {
- alternateTouchProvider
- .get()
- .onPointerDown(
- requestId,
- event.x.toInt(),
- event.y.toInt(),
- event.touchMinor,
- event.touchMajor
- )
- }
- fgExecutor.execute {
- if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
- keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
+ view.processMotionEvent(event)
+
+ val goodOverlap =
+ if (featureFlags.isEnabled(Flags.NEW_ELLIPSE_DETECTION)) {
+ isGoodEllipseOverlap(event)
+ } else {
+ isGoodCentroidOverlap(event)
}
- }
- view.configureDisplay {
- biometricExecutor.execute { alternateTouchProvider.get().onUiReady() }
+ if (!processedMotionEvent && goodOverlap) {
+ biometricExecutor.execute {
+ alternateTouchProvider
+ .get()
+ .onPointerDown(
+ requestId,
+ event.rawX.toInt(),
+ event.rawY.toInt(),
+ event.touchMinor,
+ event.touchMajor
+ )
+ }
+ fgExecutor.execute {
+ if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+ keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
+ }
+
+ view.configureDisplay {
+ biometricExecutor.execute {
+ alternateTouchProvider.get().onUiReady()
+ }
+ }
+
+ processedMotionEvent = true
+ }
}
- }
+ view.invalidate()
+ }
true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
- if (onFingerDown && alternateTouchProvider.isPresent) {
+ if (processedMotionEvent && alternateTouchProvider.isPresent) {
biometricExecutor.execute {
alternateTouchProvider.get().onPointerUp(requestId)
}
@@ -130,43 +165,105 @@ constructor(
keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
}
}
+
+ processedMotionEvent = false
}
- onFingerDown = false
+
if (view.isDisplayConfigured) {
view.unconfigureDisplay()
}
+ view.invalidate()
true
}
else -> false
}
}
- fun show(requestId: Long): Boolean {
+ fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
+ return points.count { checkPoint(event, it) } >= NEEDED_POINTS
+ }
+
+ fun isGoodCentroidOverlap(event: MotionEvent): Boolean {
+ return params.sensorBounds.contains(event.rawX.toInt(), event.rawY.toInt())
+ }
+
+ fun checkPoint(event: MotionEvent, point: Point): Boolean {
+ // Calculate if sensor point is within ellipse
+ // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
+ // yS))^2 / b^2) <= 1
+ val a: Float = cos(event.orientation) * (point.x - event.rawX)
+ val b: Float = sin(event.orientation) * (point.y - event.rawY)
+ val c: Float = sin(event.orientation) * (point.x - event.rawX)
+ val d: Float = cos(event.orientation) * (point.y - event.rawY)
+ val result =
+ (a + b).pow(2) / (event.touchMinor / 2).pow(2) +
+ (c - d).pow(2) / (event.touchMajor / 2).pow(2)
+
+ return result <= 1
+ }
+
+ fun show(requestId: Long) {
+ if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return
+ }
+
this.requestId = requestId
- if (overlayView == null && alternateTouchProvider.isPresent) {
- UdfpsOverlayView(context, null).let {
- it.overlayParams = params
- it.setUdfpsDisplayMode(
- UdfpsDisplayMode(context, execution, authController, udfpsLogger)
- )
- it.setOnTouchListener { v, event -> onTouch(v, event) }
- overlayView = it
+ fgExecutor.execute {
+ if (overlayView == null && alternateTouchProvider.isPresent) {
+ UdfpsOverlayView(context, null).let {
+ it.overlayParams = params
+ it.setUdfpsDisplayMode(
+ UdfpsDisplayMode(context, execution, authController, udfpsLogger)
+ )
+ it.setOnTouchListener { _, event -> onTouch(event) }
+ it.sensorPoints = points
+ it.debugOverlay =
+ Settings.Global.getInt(
+ context.contentResolver,
+ SETTING_OVERLAY_DEBUG,
+ 0 /* def */
+ ) != 0
+ overlayView = it
+ }
+ windowManager.addView(overlayView, coreLayoutParams)
+ isShowing = true
}
- windowManager.addView(overlayView, coreLayoutParams)
- return true
}
-
- return false
}
fun hide() {
- overlayView?.apply {
- windowManager.removeView(this)
- setOnTouchListener(null)
+ if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+ return
}
- overlayView = null
+ fgExecutor.execute {
+ if (overlayView != null && isShowing && alternateTouchProvider.isPresent) {
+ if (processedMotionEvent) {
+ biometricExecutor.execute {
+ alternateTouchProvider.get().onPointerUp(requestId)
+ }
+ fgExecutor.execute {
+ if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+ keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
+ }
+ }
+ }
+
+ if (overlayView!!.isDisplayConfigured) {
+ overlayView!!.unconfigureDisplay()
+ }
+
+ overlayView?.apply {
+ windowManager.removeView(this)
+ setOnTouchListener(null)
+ }
+
+ isShowing = false
+ overlayView = null
+ processedMotionEvent = false
+ }
+ }
}
@Override
@@ -180,6 +277,18 @@ constructor(
}
}
)
+
+ fingerprintManager?.setUdfpsOverlay(
+ object : IUdfpsOverlay.Stub() {
+ override fun show(
+ requestId: Long,
+ sensorId: Int,
+ @BiometricOverlayConstants.ShowReason reason: Int
+ ) = show(requestId)
+
+ override fun hide(sensorId: Int) = hide()
+ }
+ )
}
private fun handleAllFingerprintAuthenticatorsRegistered(
@@ -201,6 +310,24 @@ constructor(
naturalDisplayHeight = size.height(),
scaleFactor = 1f
)
+
+ val sensorX = params.sensorBounds.centerX()
+ val sensorY = params.sensorBounds.centerY()
+ val cornerOffset: Int = params.sensorBounds.width() / 4
+ val sideOffset: Int = params.sensorBounds.width() / 3
+
+ points =
+ arrayOf(
+ Point(sensorX - cornerOffset, sensorY - cornerOffset),
+ Point(sensorX, sensorY - sideOffset),
+ Point(sensorX + cornerOffset, sensorY - cornerOffset),
+ Point(sensorX - sideOffset, sensorY),
+ Point(sensorX, sensorY),
+ Point(sensorX + sideOffset, sensorY),
+ Point(sensorX - cornerOffset, sensorY + cornerOffset),
+ Point(sensorX, sensorY + sideOffset),
+ Point(sensorX + cornerOffset, sensorY + cornerOffset)
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
index d37133239531..4e6a06b1c44b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
@@ -20,26 +20,41 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
+import android.graphics.Point
import android.graphics.RectF
import android.util.AttributeSet
+import android.view.MotionEvent
import android.widget.FrameLayout
private const val TAG = "UdfpsOverlayView"
+private const val POINT_SIZE = 10f
class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
-
- private val sensorRect = RectF()
var overlayParams = UdfpsOverlayParams()
private var mUdfpsDisplayMode: UdfpsDisplayMode? = null
+ var debugOverlay = false
+
var overlayPaint = Paint()
var sensorPaint = Paint()
+ var touchPaint = Paint()
+ var pointPaint = Paint()
val centerPaint = Paint()
+ var oval = RectF()
+
/** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
var isDisplayConfigured: Boolean = false
private set
+ var touchX: Float = 0f
+ var touchY: Float = 0f
+ var touchMinor: Float = 0f
+ var touchMajor: Float = 0f
+ var touchOrientation: Double = 0.0
+
+ var sensorPoints: Array<Point>? = null
+
init {
this.setWillNotDraw(false)
}
@@ -47,24 +62,60 @@ class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(con
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- overlayPaint.color = Color.argb(120, 255, 0, 0)
+ overlayPaint.color = Color.argb(100, 255, 0, 0)
overlayPaint.style = Paint.Style.FILL
+ touchPaint.color = Color.argb(200, 255, 255, 255)
+ touchPaint.style = Paint.Style.FILL
+
sensorPaint.color = Color.argb(150, 134, 204, 255)
sensorPaint.style = Paint.Style.FILL
+
+ pointPaint.color = Color.WHITE
+ pointPaint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
- canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
+ if (debugOverlay) {
+ // Draw overlay and sensor bounds
+ canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
+ canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
+ }
+
+ // Draw sensor circle
canvas.drawCircle(
overlayParams.sensorBounds.exactCenterX(),
overlayParams.sensorBounds.exactCenterY(),
overlayParams.sensorBounds.width().toFloat() / 2,
centerPaint
)
+
+ if (debugOverlay) {
+ // Draw Points
+ sensorPoints?.forEach {
+ canvas.drawCircle(it.x.toFloat(), it.y.toFloat(), POINT_SIZE, pointPaint)
+ }
+
+ // Draw touch oval
+ canvas.save()
+ canvas.rotate(Math.toDegrees(touchOrientation).toFloat(), touchX, touchY)
+
+ oval.setEmpty()
+ oval.set(
+ touchX - touchMinor / 2,
+ touchY + touchMajor / 2,
+ touchX + touchMinor / 2,
+ touchY - touchMajor / 2
+ )
+
+ canvas.drawOval(oval, touchPaint)
+
+ // Draw center point
+ canvas.drawCircle(touchX, touchY, POINT_SIZE, centerPaint)
+ canvas.restore()
+ }
}
fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) {
@@ -80,4 +131,12 @@ class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(con
isDisplayConfigured = false
mUdfpsDisplayMode?.disable(null /* onDisabled */)
}
+
+ fun processMotionEvent(event: MotionEvent) {
+ touchX = event.rawX
+ touchY = event.rawY
+ touchMinor = event.touchMinor
+ touchMajor = event.touchMajor
+ touchOrientation = event.orientation.toDouble()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index 75640b787a62..da50f1c94f29 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -62,7 +62,6 @@ class UdfpsShell @Inject constructor(
if (args.size == 1 && args[0] == "hide") {
hideOverlay()
} else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "show") {
- hideOverlay()
showUdfpsOverlay()
} else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "hide") {
hideUdfpsOverlay()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index b5d81f253916..7c0c3b710e66 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,32 +16,45 @@
package com.android.systemui.biometrics.dagger
+import com.android.systemui.biometrics.data.repository.PromptRepository
+import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
+import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.util.concurrency.ThreadFactory
+import dagger.Binds
import dagger.Module
import dagger.Provides
import java.util.concurrent.Executor
import javax.inject.Qualifier
-/**
- * Dagger module for all things biometric.
- */
+/** Dagger module for all things biometric. */
@Module
-object BiometricsModule {
+interface BiometricsModule {
- /** Background [Executor] for HAL related operations. */
- @Provides
+ @Binds
@SysUISingleton
- @JvmStatic
- @BiometricsBackground
- fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
- threadFactory.buildExecutorOnNewThread("biometrics")
+ fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository
+
+ @Binds
+ @SysUISingleton
+ fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
+
+ companion object {
+ /** Background [Executor] for HAL related operations. */
+ @Provides
+ @SysUISingleton
+ @JvmStatic
+ @BiometricsBackground
+ fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
+ threadFactory.buildExecutorOnNewThread("biometrics")
+ }
}
/**
- * Background executor for HAL operations that are latency sensitive but too
- * slow to run on the main thread. Prefer the shared executors, such as
- * [com.android.systemui.dagger.qualifiers.Background] when a HAL is not directly involved.
+ * Background executor for HAL operations that are latency sensitive but too slow to run on the main
+ * thread. Prefer the shared executors, such as [com.android.systemui.dagger.qualifiers.Background]
+ * when a HAL is not directly involved.
*/
@Qualifier
@MustBeDocumented
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
new file mode 100644
index 000000000000..e82646f0d861
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/model/PromptKind.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.biometrics.data.model
+
+import com.android.systemui.biometrics.Utils
+
+// TODO(b/251476085): this should eventually replace Utils.CredentialType
+/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
+enum class PromptKind {
+ ANY_BIOMETRIC,
+ PIN,
+ PATTERN,
+ PASSWORD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
new file mode 100644
index 000000000000..92a13cfe538b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -0,0 +1,102 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * A repository for the global state of BiometricPrompt.
+ *
+ * There is never more than one instance of the prompt at any given time.
+ */
+interface PromptRepository {
+
+ /** If the prompt is showing. */
+ val isShowing: Flow<Boolean>
+
+ /** The app-specific details to show in the prompt. */
+ val promptInfo: StateFlow<PromptInfo?>
+
+ /** The user that the prompt is for. */
+ val userId: StateFlow<Int?>
+
+ /** The gatekeeper challenge, if one is associated with this prompt. */
+ val challenge: StateFlow<Long?>
+
+ /** The kind of credential to use (biometric, pin, pattern, etc.). */
+ val kind: StateFlow<PromptKind>
+
+ /** Update the prompt configuration, which should be set before [isShowing]. */
+ fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind = PromptKind.ANY_BIOMETRIC,
+ )
+
+ /** Unset the prompt info. */
+ fun unsetPrompt()
+}
+
+@SysUISingleton
+class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
+ PromptRepository {
+
+ override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
+ val callback =
+ object : AuthController.Callback {
+ override fun onBiometricPromptShown() =
+ trySendWithFailureLogging(true, TAG, "set isShowing")
+
+ override fun onBiometricPromptDismissed() =
+ trySendWithFailureLogging(false, TAG, "unset isShowing")
+ }
+ authController.addCallback(callback)
+ trySendWithFailureLogging(authController.isShowing, TAG, "update isShowing")
+ awaitClose { authController.removeCallback(callback) }
+ }
+
+ private val _promptInfo: MutableStateFlow<PromptInfo?> = MutableStateFlow(null)
+ override val promptInfo = _promptInfo.asStateFlow()
+
+ private val _challenge: MutableStateFlow<Long?> = MutableStateFlow(null)
+ override val challenge: StateFlow<Long?> = _challenge.asStateFlow()
+
+ private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null)
+ override val userId = _userId.asStateFlow()
+
+ private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ override val kind = _kind.asStateFlow()
+
+ override fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind,
+ ) {
+ _kind.value = kind
+ _userId.value = userId
+ _challenge.value = gatekeeperChallenge
+ _promptInfo.value = promptInfo
+ }
+
+ override fun unsetPrompt() {
+ _promptInfo.value = null
+ _userId.value = null
+ _challenge.value = null
+ _kind.value = PromptKind.ANY_BIOMETRIC
+ }
+
+ companion object {
+ private const val TAG = "BiometricPromptRepository"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
new file mode 100644
index 000000000000..1f1a1b5c83bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -0,0 +1,282 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources
+import android.content.Context
+import android.os.UserManager
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.R
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * A wrapper for [LockPatternUtils] to verify PIN, pattern, or password credentials.
+ *
+ * This class also uses the [DevicePolicyManager] to generate appropriate error messages when policy
+ * exceptions are raised (i.e. wipe device due to excessive failed attempts, etc.).
+ */
+interface CredentialInteractor {
+ /** If the user's pattern credential should be hidden */
+ fun isStealthModeActive(userId: Int): Boolean
+
+ /** Get the effective user id (profile owner, if one exists) */
+ fun getCredentialOwnerOrSelfId(userId: Int): Int
+
+ /**
+ * Verifies a credential and returns a stream of results.
+ *
+ * The final emitted value will either be a [CredentialStatus.Fail.Error] or a
+ * [CredentialStatus.Success.Verified].
+ */
+ fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus>
+}
+
+/** Standard implementation of [CredentialInteractor]. */
+class CredentialInteractorImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val lockPatternUtils: LockPatternUtils,
+ private val userManager: UserManager,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val systemClock: SystemClock,
+) : CredentialInteractor {
+
+ override fun isStealthModeActive(userId: Int): Boolean =
+ !lockPatternUtils.isVisiblePatternEnabled(userId)
+
+ override fun getCredentialOwnerOrSelfId(userId: Int): Int =
+ userManager.getCredentialOwnerProfile(userId)
+
+ override fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus> = flow {
+ // Request LockSettingsService to return the Gatekeeper Password in the
+ // VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
+ // Gatekeeper Password and operationId.
+ val effectiveUserId = request.userInfo.deviceCredentialOwnerId
+ val response =
+ lockPatternUtils.verifyCredential(
+ credential,
+ effectiveUserId,
+ LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE
+ )
+
+ if (response.isMatched) {
+ lockPatternUtils.userPresent(effectiveUserId)
+
+ // The response passed into this method contains the Gatekeeper
+ // Password. We still have to request Gatekeeper to create a
+ // Hardware Auth Token with the Gatekeeper Password and Challenge
+ // (keystore operationId in this case)
+ val pwHandle = response.gatekeeperPasswordHandle
+ val gkResponse: VerifyCredentialResponse =
+ lockPatternUtils.verifyGatekeeperPasswordHandle(
+ pwHandle,
+ request.operationInfo.gatekeeperChallenge,
+ effectiveUserId
+ )
+ val hat = gkResponse.gatekeeperHAT
+ lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
+ emit(CredentialStatus.Success.Verified(hat))
+ } else if (response.timeout > 0) {
+ // if requests are being throttled, update the error message every
+ // second until the temporary lock has expired
+ val deadline: Long =
+ lockPatternUtils.setLockoutAttemptDeadline(effectiveUserId, response.timeout)
+ val interval = LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS
+ var remaining = deadline - systemClock.elapsedRealtime()
+ while (remaining > 0) {
+ emit(
+ CredentialStatus.Fail.Throttled(
+ applicationContext.getString(
+ R.string.biometric_dialog_credential_too_many_attempts,
+ remaining / 1000
+ )
+ )
+ )
+ delay(interval)
+ remaining -= interval
+ }
+ emit(CredentialStatus.Fail.Error(""))
+ } else { // bad request, but not throttled
+ val numAttempts = lockPatternUtils.getCurrentFailedPasswordAttempts(effectiveUserId) + 1
+ val maxAttempts = lockPatternUtils.getMaximumFailedPasswordsForWipe(effectiveUserId)
+ if (maxAttempts <= 0 || numAttempts <= 0) {
+ // use a generic message if there's no maximum number of attempts
+ emit(CredentialStatus.Fail.Error())
+ } else {
+ val remainingAttempts = (maxAttempts - numAttempts).coerceAtLeast(0)
+ emit(
+ CredentialStatus.Fail.Error(
+ applicationContext.getString(
+ R.string.biometric_dialog_credential_attempts_before_wipe,
+ numAttempts,
+ maxAttempts
+ ),
+ remainingAttempts,
+ fetchFinalAttemptMessageOrNull(request, remainingAttempts)
+ )
+ )
+ }
+ lockPatternUtils.reportFailedPasswordAttempt(effectiveUserId)
+ }
+ }
+
+ private fun fetchFinalAttemptMessageOrNull(
+ request: BiometricPromptRequest.Credential,
+ remainingAttempts: Int?,
+ ): String? =
+ if (remainingAttempts != null && remainingAttempts <= 1) {
+ applicationContext.getFinalAttemptMessageOrBlank(
+ request,
+ devicePolicyManager,
+ userManager.getUserTypeForWipe(
+ devicePolicyManager,
+ request.userInfo.deviceCredentialOwnerId
+ ),
+ remainingAttempts
+ )
+ } else {
+ null
+ }
+}
+
+private enum class UserType {
+ PRIMARY,
+ MANAGED_PROFILE,
+ SECONDARY,
+}
+
+private fun UserManager.getUserTypeForWipe(
+ devicePolicyManager: DevicePolicyManager,
+ effectiveUserId: Int,
+): UserType {
+ val userToBeWiped =
+ getUserInfo(
+ devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(effectiveUserId)
+ )
+ return when {
+ userToBeWiped == null || userToBeWiped.isPrimary -> UserType.PRIMARY
+ userToBeWiped.isManagedProfile -> UserType.MANAGED_PROFILE
+ else -> UserType.SECONDARY
+ }
+}
+
+private fun Context.getFinalAttemptMessageOrBlank(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+ remaining: Int,
+): String =
+ when {
+ remaining == 1 -> getLastAttemptBeforeWipeMessage(request, devicePolicyManager, userType)
+ remaining <= 0 -> getNowWipingMessage(devicePolicyManager, userType)
+ else -> ""
+ }
+
+private fun Context.getLastAttemptBeforeWipeMessage(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+): String =
+ when (userType) {
+ UserType.PRIMARY -> getLastAttemptBeforeWipeDeviceMessage(request)
+ UserType.MANAGED_PROFILE ->
+ getLastAttemptBeforeWipeProfileMessage(request, devicePolicyManager)
+ UserType.SECONDARY -> getLastAttemptBeforeWipeUserMessage(request)
+ }
+
+private fun Context.getLastAttemptBeforeWipeDeviceMessage(
+ request: BiometricPromptRequest.Credential,
+): String {
+ val id =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_device
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_device
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_device
+ }
+ return getString(id)
+}
+
+private fun Context.getLastAttemptBeforeWipeProfileMessage(
+ request: BiometricPromptRequest.Credential,
+ devicePolicyManager: DevicePolicyManager,
+): String {
+ val id =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT
+ is BiometricPromptRequest.Credential.Pattern ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT
+ is BiometricPromptRequest.Credential.Password ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
+ }
+ return devicePolicyManager.resources.getString(id) {
+ // use fallback a string if not found
+ val defaultId =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_profile
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_profile
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_profile
+ }
+ getString(defaultId)
+ }
+}
+
+private fun Context.getLastAttemptBeforeWipeUserMessage(
+ request: BiometricPromptRequest.Credential,
+): String {
+ val resId =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ R.string.biometric_dialog_last_pin_attempt_before_wipe_user
+ is BiometricPromptRequest.Credential.Pattern ->
+ R.string.biometric_dialog_last_pattern_attempt_before_wipe_user
+ is BiometricPromptRequest.Credential.Password ->
+ R.string.biometric_dialog_last_password_attempt_before_wipe_user
+ }
+ return getString(resId)
+}
+
+private fun Context.getNowWipingMessage(
+ devicePolicyManager: DevicePolicyManager,
+ userType: UserType,
+): String {
+ val id =
+ when (userType) {
+ UserType.MANAGED_PROFILE ->
+ DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
+ else -> DevicePolicyResources.UNDEFINED
+ }
+ return devicePolicyManager.resources.getString(id) {
+ // use fallback a string if not found
+ val defaultId =
+ when (userType) {
+ UserType.PRIMARY ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_device
+ UserType.MANAGED_PROFILE ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_profile
+ UserType.SECONDARY ->
+ com.android.settingslib.R.string.failed_attempts_now_wiping_user
+ }
+ getString(defaultId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt
new file mode 100644
index 000000000000..40b76121f237
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialStatus.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.biometrics.domain.interactor
+
+/** Result of a [CredentialInteractor.verifyCredential] check. */
+sealed interface CredentialStatus {
+ /** A successful result. */
+ sealed interface Success : CredentialStatus {
+ /** The credential is valid and a [hat] has been generated. */
+ data class Verified(val hat: ByteArray) : Success
+ }
+ /** A failed result. */
+ sealed interface Fail : CredentialStatus {
+ val error: String?
+
+ /** The credential check failed with an [error]. */
+ data class Error(
+ override val error: String? = null,
+ val remainingAttempts: Int? = null,
+ val urgentMessage: String? = null,
+ ) : Fail
+ /** The credential check failed with an [error] and is temporarily locked out. */
+ data class Throttled(override val error: String) : Fail
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
new file mode 100644
index 000000000000..6362c2f627d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -0,0 +1,189 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.PromptInfo
+import com.android.internal.widget.LockPatternView
+import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.data.repository.PromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.lastOrNull
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
+
+/**
+ * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users
+ * PIN, pattern, or password credential instead of a biometric.
+ */
+class BiometricPromptCredentialInteractor
+@Inject
+constructor(
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val biometricPromptRepository: PromptRepository,
+ private val credentialInteractor: CredentialInteractor,
+) {
+ /** If the prompt is currently showing. */
+ val isShowing: Flow<Boolean> = biometricPromptRepository.isShowing
+
+ /** Metadata about the current credential prompt, including app-supplied preferences. */
+ val prompt: Flow<BiometricPromptRequest?> =
+ combine(
+ biometricPromptRepository.promptInfo,
+ biometricPromptRepository.challenge,
+ biometricPromptRepository.userId,
+ biometricPromptRepository.kind
+ ) { promptInfo, challenge, userId, kind ->
+ if (promptInfo == null || userId == null || challenge == null) {
+ return@combine null
+ }
+
+ when (kind) {
+ PromptKind.PIN ->
+ BiometricPromptRequest.Credential.Pin(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge)
+ )
+ PromptKind.PATTERN ->
+ BiometricPromptRequest.Credential.Pattern(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge),
+ stealthMode = credentialInteractor.isStealthModeActive(userId)
+ )
+ PromptKind.PASSWORD ->
+ BiometricPromptRequest.Credential.Password(
+ info = promptInfo,
+ userInfo = userInfo(userId),
+ operationInfo = operationInfo(challenge)
+ )
+ else -> null
+ }
+ }
+ .distinctUntilChanged()
+
+ private fun userInfo(userId: Int): BiometricUserInfo =
+ BiometricUserInfo(
+ userId = userId,
+ deviceCredentialOwnerId = credentialInteractor.getCredentialOwnerOrSelfId(userId)
+ )
+
+ private fun operationInfo(challenge: Long): BiometricOperationInfo =
+ BiometricOperationInfo(gatekeeperChallenge = challenge)
+
+ /** Most recent error due to [verifyCredential]. */
+ private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null)
+ val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow()
+
+ /** Update the current request to use credential-based authentication instead of biometrics. */
+ fun useCredentialsForAuthentication(
+ promptInfo: PromptInfo,
+ @Utils.CredentialType kind: Int,
+ userId: Int,
+ challenge: Long,
+ ) {
+ biometricPromptRepository.setPrompt(
+ promptInfo,
+ userId,
+ challenge,
+ kind.asBiometricPromptCredential()
+ )
+ }
+
+ /** Unset the current authentication request. */
+ fun resetPrompt() {
+ biometricPromptRepository.unsetPrompt()
+ }
+
+ /**
+ * Check a credential and return the attestation token (HAT) if successful.
+ *
+ * This method will not return if credential checks are being throttled until the throttling has
+ * expired and the user can try again. It will periodically update the [verificationError] until
+ * cancelled or the throttling has completed. If the request is not throttled, but unsuccessful,
+ * the [verificationError] will be set and an optional
+ * [CredentialStatus.Fail.Error.urgentMessage] message may be provided to indicate additional
+ * hints to the user (i.e. device will be wiped on next failure, etc.).
+ *
+ * The check happens on the background dispatcher given in the constructor.
+ */
+ suspend fun checkCredential(
+ request: BiometricPromptRequest.Credential,
+ text: CharSequence? = null,
+ pattern: List<LockPatternView.Cell>? = null,
+ ): CredentialStatus =
+ withContext(bgDispatcher) {
+ val credential =
+ when (request) {
+ is BiometricPromptRequest.Credential.Pin ->
+ LockscreenCredential.createPinOrNone(text ?: "")
+ is BiometricPromptRequest.Credential.Password ->
+ LockscreenCredential.createPasswordOrNone(text ?: "")
+ is BiometricPromptRequest.Credential.Pattern ->
+ LockscreenCredential.createPattern(pattern ?: listOf())
+ }
+
+ credential.use { c -> verifyCredential(request, c) }
+ }
+
+ private suspend fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential?
+ ): CredentialStatus {
+ if (credential == null || credential.isNone) {
+ return CredentialStatus.Fail.Error()
+ }
+
+ val finalStatus =
+ credentialInteractor
+ .verifyCredential(request, credential)
+ .onEach { status ->
+ when (status) {
+ is CredentialStatus.Success -> _verificationError.value = null
+ is CredentialStatus.Fail -> _verificationError.value = status
+ }
+ }
+ .lastOrNull()
+
+ return finalStatus ?: CredentialStatus.Fail.Error()
+ }
+
+ /**
+ * Report a user-visible error.
+ *
+ * Use this instead of calling [verifyCredential] when it is not necessary because the check
+ * will obviously fail (i.e. too short, empty, etc.)
+ */
+ fun setVerificationError(error: CredentialStatus.Fail.Error?) {
+ if (error != null) {
+ _verificationError.value = error
+ } else {
+ resetVerificationError()
+ }
+ }
+
+ /** Clear the current error message, if any. */
+ fun resetVerificationError() {
+ _verificationError.value = null
+ }
+}
+
+// TODO(b/251476085): remove along with Utils.CredentialType
+/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */
+private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind =
+ when (this) {
+ Utils.CREDENTIAL_PIN -> PromptKind.PIN
+ Utils.CREDENTIAL_PASSWORD -> PromptKind.PASSWORD
+ Utils.CREDENTIAL_PATTERN -> PromptKind.PATTERN
+ else -> PromptKind.ANY_BIOMETRIC
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt
new file mode 100644
index 000000000000..c619b12361c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricOperationInfo.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.biometrics.domain.model
+
+/** Metadata about an in-progress biometric operation. */
+data class BiometricOperationInfo(val gatekeeperChallenge: Long = -1)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
new file mode 100644
index 000000000000..5ee0381db630
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -0,0 +1,69 @@
+package com.android.systemui.biometrics.domain.model
+
+import android.hardware.biometrics.PromptInfo
+
+/**
+ * Preferences for BiometricPrompt, such as title & description, that are immutable while the prompt
+ * is showing.
+ *
+ * This roughly corresponds to a "request" by the system or an app to show BiometricPrompt and it
+ * contains a subset of the information in a [PromptInfo] that is relevant to SysUI.
+ */
+sealed class BiometricPromptRequest(
+ val title: String,
+ val subtitle: String,
+ val description: String,
+ val userInfo: BiometricUserInfo,
+ val operationInfo: BiometricOperationInfo,
+) {
+ /** Prompt using one or more biometrics. */
+ class Biometric(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) :
+ BiometricPromptRequest(
+ title = info.title?.toString() ?: "",
+ subtitle = info.subtitle?.toString() ?: "",
+ description = info.description?.toString() ?: "",
+ userInfo = userInfo,
+ operationInfo = operationInfo
+ )
+
+ /** Prompt using a credential (pin, pattern, password). */
+ sealed class Credential(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) :
+ BiometricPromptRequest(
+ title = (info.deviceCredentialTitle ?: info.title)?.toString() ?: "",
+ subtitle = (info.deviceCredentialSubtitle ?: info.subtitle)?.toString() ?: "",
+ description = (info.deviceCredentialDescription ?: info.description)?.toString() ?: "",
+ userInfo = userInfo,
+ operationInfo = operationInfo,
+ ) {
+
+ /** PIN prompt. */
+ class Pin(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) : Credential(info, userInfo, operationInfo)
+
+ /** Password prompt. */
+ class Password(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ ) : Credential(info, userInfo, operationInfo)
+
+ /** Pattern prompt. */
+ class Pattern(
+ info: PromptInfo,
+ userInfo: BiometricUserInfo,
+ operationInfo: BiometricOperationInfo,
+ val stealthMode: Boolean,
+ ) : Credential(info, userInfo, operationInfo)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
new file mode 100644
index 000000000000..08da04d27606
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricUserInfo.kt
@@ -0,0 +1,7 @@
+package com.android.systemui.biometrics.domain.model
+
+/** Metadata about the current user BiometricPrompt is being shown to. */
+data class BiometricUserInfo(
+ val userId: Int,
+ val deviceCredentialOwnerId: Int = userId,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
new file mode 100644
index 000000000000..bcc0575651e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -0,0 +1,130 @@
+package com.android.systemui.biometrics.ui
+
+import android.content.Context
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.View
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.ime
+import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
+import android.widget.ImeAwareEditText
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.view.isGone
+import com.android.systemui.R
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** PIN or password credential view for BiometricPrompt. */
+class CredentialPasswordView(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs), CredentialView, View.OnApplyWindowInsetsListener {
+
+ private lateinit var titleView: TextView
+ private lateinit var subtitleView: TextView
+ private lateinit var descriptionView: TextView
+ private lateinit var iconView: ImageView
+ private lateinit var passwordField: ImeAwareEditText
+ private lateinit var credentialHeader: View
+ private lateinit var credentialInput: View
+
+ private var bottomInset: Int = 0
+
+ private val accessibilityManager by lazy {
+ context.getSystemService(AccessibilityManager::class.java)
+ }
+
+ /** Initializes the view. */
+ override fun init(
+ viewModel: CredentialViewModel,
+ host: CredentialView.Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ ) {
+ CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel)
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+
+ titleView = requireViewById(R.id.title)
+ subtitleView = requireViewById(R.id.subtitle)
+ descriptionView = requireViewById(R.id.description)
+ iconView = requireViewById(R.id.icon)
+ subtitleView = requireViewById(R.id.subtitle)
+ passwordField = requireViewById(R.id.lockPassword)
+ credentialHeader = requireViewById(R.id.auth_credential_header)
+ credentialInput = requireViewById(R.id.auth_credential_input)
+
+ setOnApplyWindowInsetsListener(this)
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+
+ val inputLeftBound: Int
+ val inputTopBound: Int
+ var headerRightBound = right
+ var headerTopBounds = top
+ val subTitleBottom: Int = if (subtitleView.isGone) titleView.bottom else subtitleView.bottom
+ val descBottom = if (descriptionView.isGone) subTitleBottom else descriptionView.bottom
+ if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ inputTopBound = (bottom - credentialInput.height) / 2
+ inputLeftBound = (right - left) / 2
+ headerRightBound = inputLeftBound
+ headerTopBounds -= iconView.bottom.coerceAtMost(bottomInset)
+ } else {
+ inputTopBound = descBottom + (bottom - descBottom - credentialInput.height) / 2
+ inputLeftBound = (right - left - credentialInput.width) / 2
+ }
+
+ if (descriptionView.bottom > bottomInset) {
+ credentialHeader.layout(left, headerTopBounds, headerRightBound, bottom)
+ }
+ credentialInput.layout(inputLeftBound, inputTopBound, right, bottom)
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+
+ val newWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val newHeight = MeasureSpec.getSize(heightMeasureSpec) - bottomInset
+
+ setMeasuredDimension(newWidth, newHeight)
+
+ val halfWidthSpec = MeasureSpec.makeMeasureSpec(width / 2, MeasureSpec.AT_MOST)
+ val fullHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.UNSPECIFIED)
+ if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ measureChildren(halfWidthSpec, fullHeightSpec)
+ } else {
+ measureChildren(widthMeasureSpec, fullHeightSpec)
+ }
+ }
+
+ override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets {
+ val bottomInsets = insets.getInsets(ime())
+ if (bottomInset != bottomInsets.bottom) {
+ bottomInset = bottomInsets.bottom
+
+ if (bottomInset > 0 && resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
+ titleView.isSingleLine = true
+ titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
+ titleView.marqueeRepeatLimit = -1
+ // select to enable marquee unless a screen reader is enabled
+ titleView.isSelected = accessibilityManager.shouldMarquee()
+ } else {
+ titleView.isSingleLine = false
+ titleView.ellipsize = null
+ // select to enable marquee unless a screen reader is enabled
+ titleView.isSelected = false
+ }
+
+ requestLayout()
+ }
+ return insets
+ }
+}
+
+private fun AccessibilityManager.shouldMarquee(): Boolean = !isEnabled || !isTouchExplorationEnabled
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
new file mode 100644
index 000000000000..75331f083851
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPatternView.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.biometrics.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.binder.CredentialViewBinder
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** Pattern credential view for BiometricPrompt. */
+class CredentialPatternView(context: Context, attrs: AttributeSet?) :
+ LinearLayout(context, attrs), CredentialView {
+
+ /** Initializes the view. */
+ override fun init(
+ viewModel: CredentialViewModel,
+ host: CredentialView.Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ ) {
+ CredentialViewBinder.bind(this, host, viewModel, panelViewController, animatePanel)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
new file mode 100644
index 000000000000..b7c6a4566108
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialView.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.biometrics.ui
+
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+
+/** A credential variant of BiometricPrompt. */
+sealed interface CredentialView {
+ /**
+ * Callbacks for the "host" container view that contains this credential view.
+ *
+ * TODO(b/251476085): Removed when the host view is converted to use a parent view model.
+ */
+ interface Host {
+ /** When the user's credential has been verified. */
+ fun onCredentialMatched(attestation: ByteArray)
+
+ /** When the user abandons credential verification. */
+ fun onCredentialAborted()
+
+ /** Warn the user is warned about excessive attempts. */
+ fun onCredentialAttemptsRemaining(remaining: Int, messageBody: String)
+ }
+
+ // TODO(251476085): remove AuthPanelController
+ fun init(
+ viewModel: CredentialViewModel,
+ host: Host,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
new file mode 100644
index 000000000000..c619648a314c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -0,0 +1,104 @@
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.KeyEvent
+import android.view.View
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputMethodManager
+import android.widget.ImeAwareEditText
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.CredentialPasswordView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Sub-binder for the [CredentialPasswordView]. */
+object CredentialPasswordViewBinder {
+
+ /** Bind the view. */
+ fun bind(
+ view: CredentialPasswordView,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ ) {
+ val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
+
+ val passwordField: ImeAwareEditText = view.requireViewById(R.id.lockPassword)
+
+ view.repeatWhenAttached {
+ passwordField.requestFocus()
+ passwordField.scheduleShowSoftInput()
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // observe credential validation attempts and submit/cancel buttons
+ launch {
+ viewModel.header.collect { header ->
+ passwordField.setTextOperationUser(header.user)
+ passwordField.setOnEditorActionListener(
+ OnImeSubmitListener { text ->
+ launch { viewModel.checkCredential(text, header) }
+ }
+ )
+ passwordField.setOnKeyListener(
+ OnBackButtonListener { host.onCredentialAborted() }
+ )
+ }
+ }
+
+ launch {
+ viewModel.inputFlags.collect { flags ->
+ flags?.let { passwordField.inputType = it }
+ }
+ }
+
+ // dismiss on a valid credential check
+ launch {
+ viewModel.validatedAttestation.collect { attestation ->
+ if (attestation != null) {
+ imeManager.hideSoftInputFromWindow(view.windowToken, 0 /* flags */)
+ host.onCredentialMatched(attestation)
+ } else {
+ passwordField.setText("")
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private class OnBackButtonListener(private val onBack: () -> Unit) : View.OnKeyListener {
+ override fun onKey(v: View, keyCode: Int, event: KeyEvent): Boolean {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return false
+ }
+ if (event.action == KeyEvent.ACTION_UP) {
+ onBack()
+ }
+ return true
+ }
+}
+
+private class OnImeSubmitListener(private val onSubmit: (text: CharSequence) -> Unit) :
+ TextView.OnEditorActionListener {
+ override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent?): Boolean {
+ val isSoftImeEvent =
+ event == null &&
+ (actionId == EditorInfo.IME_NULL ||
+ actionId == EditorInfo.IME_ACTION_DONE ||
+ actionId == EditorInfo.IME_ACTION_NEXT)
+ val isKeyboardEnterKey =
+ event != null &&
+ KeyEvent.isConfirmKey(event.keyCode) &&
+ event.action == KeyEvent.ACTION_DOWN
+ if (isSoftImeEvent || isKeyboardEnterKey) {
+ onSubmit(v.text)
+ return true
+ }
+ return false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
new file mode 100644
index 000000000000..4765551df3f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -0,0 +1,75 @@
+package com.android.systemui.biometrics.ui.binder
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.biometrics.ui.CredentialPatternView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Sub-binder for the [CredentialPatternView]. */
+object CredentialPatternViewBinder {
+
+ /** Bind the view. */
+ fun bind(
+ view: CredentialPatternView,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ ) {
+ val lockPatternView: LockPatternView = view.requireViewById(R.id.lockPattern)
+
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // observe credential validation attempts and submit/cancel buttons
+ launch {
+ viewModel.header.collect { header ->
+ lockPatternView.setOnPatternListener(
+ OnPatternDetectedListener { pattern ->
+ if (pattern.isPatternLongEnough()) {
+ // Pattern size is less than the minimum
+ // do not count it as a failed attempt
+ viewModel.showPatternTooShortError()
+ } else {
+ lockPatternView.isEnabled = false
+ launch { viewModel.checkCredential(pattern, header) }
+ }
+ }
+ )
+ }
+ }
+
+ launch { viewModel.stealthMode.collect { lockPatternView.isInStealthMode = it } }
+
+ // dismiss on a valid credential check
+ launch {
+ viewModel.validatedAttestation.collect { attestation ->
+ val matched = attestation != null
+ lockPatternView.isEnabled = !matched
+ if (matched) {
+ host.onCredentialMatched(attestation!!)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+private class OnPatternDetectedListener(
+ private val onDetected: (pattern: List<LockPatternView.Cell>) -> Unit
+) : LockPatternView.OnPatternListener {
+ override fun onPatternCellAdded(pattern: List<LockPatternView.Cell>) {}
+ override fun onPatternCleared() {}
+ override fun onPatternStart() {}
+ override fun onPatternDetected(pattern: List<LockPatternView.Cell>) {
+ onDetected(pattern)
+ }
+}
+
+private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+ size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
new file mode 100644
index 000000000000..fcc948756972
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -0,0 +1,140 @@
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.biometrics.AuthDialog
+import com.android.systemui.biometrics.AuthPanelController
+import com.android.systemui.biometrics.ui.CredentialPasswordView
+import com.android.systemui.biometrics.ui.CredentialPatternView
+import com.android.systemui.biometrics.ui.CredentialView
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * View binder for all credential variants of BiometricPrompt, including [CredentialPatternView] and
+ * [CredentialPasswordView].
+ *
+ * This binder delegates to sub-binders for each variant, such as the [CredentialPasswordViewBinder]
+ * and [CredentialPatternViewBinder].
+ */
+object CredentialViewBinder {
+
+ /** Binds a [CredentialPasswordView] or [CredentialPatternView] to a [CredentialViewModel]. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ host: CredentialView.Host,
+ viewModel: CredentialViewModel,
+ panelViewController: AuthPanelController,
+ animatePanel: Boolean,
+ maxErrorDuration: Long = 3_000L,
+ ) {
+ val titleView: TextView = view.requireViewById(R.id.title)
+ val subtitleView: TextView = view.requireViewById(R.id.subtitle)
+ val descriptionView: TextView = view.requireViewById(R.id.description)
+ val iconView: ImageView? = view.findViewById(R.id.icon)
+ val errorView: TextView = view.requireViewById(R.id.error)
+
+ var errorTimer: Job? = null
+
+ // bind common elements
+ view.repeatWhenAttached {
+ if (animatePanel) {
+ with(panelViewController) {
+ // Credential view is always full screen.
+ setUseFullScreen(true)
+ updateForContentDimensions(
+ containerWidth,
+ containerHeight,
+ 0 /* animateDurationMs */
+ )
+ }
+ }
+
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ // show prompt metadata
+ launch {
+ viewModel.header.collect { header ->
+ titleView.text = header.title
+ view.announceForAccessibility(header.title)
+
+ subtitleView.textOrHide = header.subtitle
+ descriptionView.textOrHide = header.description
+
+ iconView?.setImageDrawable(header.icon)
+
+ // Only animate this if we're transitioning from a biometric view.
+ if (viewModel.animateContents.value) {
+ view.animateCredentialViewIn()
+ }
+ }
+ }
+
+ // show transient error messages
+ launch {
+ viewModel.errorMessage
+ .onEach { msg ->
+ errorTimer?.cancel()
+ if (msg.isNotBlank()) {
+ errorTimer = launch {
+ delay(maxErrorDuration)
+ viewModel.resetErrorMessage()
+ }
+ }
+ }
+ .collect { errorView.textOrHide = it }
+ }
+
+ // show an extra dialog if the remaining attempts becomes low
+ launch {
+ viewModel.remainingAttempts
+ .filter { it.remaining != null }
+ .collect { info ->
+ host.onCredentialAttemptsRemaining(info.remaining!!, info.message)
+ }
+ }
+ }
+ }
+
+ // bind the auth widget
+ when (view) {
+ is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+ is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
+ else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
+ }
+ }
+}
+
+private fun View.animateCredentialViewIn() {
+ translationY = resources.getDimension(R.dimen.biometric_dialog_credential_translation_offset)
+ alpha = 0f
+ postOnAnimation {
+ animate()
+ .translationY(0f)
+ .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS.toLong())
+ .alpha(1f)
+ .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
+ .withLayer()
+ .start()
+ }
+}
+
+private var TextView.textOrHide: String?
+ set(value) {
+ val gone = value.isNullOrBlank()
+ visibility = if (gone) View.GONE else View.VISIBLE
+ text = if (gone) "" else value
+ }
+ get() = text?.toString()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
new file mode 100644
index 000000000000..84bbceb38fa7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModel.kt
@@ -0,0 +1,178 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.text.InputType
+import com.android.internal.widget.LockPatternView
+import com.android.systemui.R
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialStatus
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlin.reflect.KClass
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.map
+
+/** View-model for all CredentialViews within BiometricPrompt. */
+class CredentialViewModel
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ private val credentialInteractor: BiometricPromptCredentialInteractor,
+) {
+
+ /** Top level information about the prompt. */
+ val header: Flow<HeaderViewModel> =
+ credentialInteractor.prompt.filterIsInstance<BiometricPromptRequest.Credential>().map {
+ request ->
+ BiometricPromptHeaderViewModelImpl(
+ request,
+ user = UserHandle.of(request.userInfo.userId),
+ title = request.title,
+ subtitle = request.subtitle,
+ description = request.description,
+ icon = applicationContext.asLockIcon(request.userInfo.deviceCredentialOwnerId),
+ )
+ }
+
+ /** Input flags for text based credential views */
+ val inputFlags: Flow<Int?> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pin ->
+ InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
+ else -> null
+ }
+ }
+
+ /** If stealth mode is active (hide user credential input). */
+ val stealthMode: Flow<Boolean> =
+ credentialInteractor.prompt.map {
+ when (it) {
+ is BiometricPromptRequest.Credential.Pattern -> it.stealthMode
+ else -> false
+ }
+ }
+
+ private val _animateContents: MutableStateFlow<Boolean> = MutableStateFlow(true)
+ /** If this view should be animated on transitions. */
+ val animateContents = _animateContents.asStateFlow()
+
+ /** Error messages to show the user. */
+ val errorMessage: Flow<String> =
+ combine(credentialInteractor.verificationError, credentialInteractor.prompt) { error, p ->
+ when (error) {
+ is CredentialStatus.Fail.Error -> error.error
+ ?: applicationContext.asBadCredentialErrorMessage(p)
+ is CredentialStatus.Fail.Throttled -> error.error
+ null -> ""
+ }
+ }
+
+ private val _validatedAttestation: MutableSharedFlow<ByteArray?> = MutableSharedFlow()
+ /** Results of [checkPatternCredential]. A non-null attestation is supplied on success. */
+ val validatedAttestation: Flow<ByteArray?> = _validatedAttestation.asSharedFlow()
+
+ private val _remainingAttempts: MutableStateFlow<RemainingAttempts> =
+ MutableStateFlow(RemainingAttempts())
+ /** If set, the number of remaining attempts before the user must stop. */
+ val remainingAttempts: Flow<RemainingAttempts> = _remainingAttempts.asStateFlow()
+
+ /** Enable transition animations. */
+ fun setAnimateContents(animate: Boolean) {
+ _animateContents.value = animate
+ }
+
+ /** Show an error message to inform the user the pattern is too short to attempt validation. */
+ fun showPatternTooShortError() {
+ credentialInteractor.setVerificationError(
+ CredentialStatus.Fail.Error(
+ applicationContext.asBadCredentialErrorMessage(
+ BiometricPromptRequest.Credential.Pattern::class
+ )
+ )
+ )
+ }
+
+ /** Reset the error message to an empty string. */
+ fun resetErrorMessage() {
+ credentialInteractor.resetVerificationError()
+ }
+
+ /** Check a PIN or password and update [validatedAttestation] or [remainingAttempts]. */
+ suspend fun checkCredential(text: CharSequence, header: HeaderViewModel) =
+ checkCredential(credentialInteractor.checkCredential(header.asRequest(), text = text))
+
+ /** Check a pattern and update [validatedAttestation] or [remainingAttempts]. */
+ suspend fun checkCredential(pattern: List<LockPatternView.Cell>, header: HeaderViewModel) =
+ checkCredential(credentialInteractor.checkCredential(header.asRequest(), pattern = pattern))
+
+ private suspend fun checkCredential(result: CredentialStatus) {
+ when (result) {
+ is CredentialStatus.Success.Verified -> {
+ _validatedAttestation.emit(result.hat)
+ _remainingAttempts.value = RemainingAttempts()
+ }
+ is CredentialStatus.Fail.Error -> {
+ _validatedAttestation.emit(null)
+ _remainingAttempts.value =
+ RemainingAttempts(result.remainingAttempts, result.urgentMessage ?: "")
+ }
+ is CredentialStatus.Fail.Throttled -> {
+ // required for completeness, but a throttled error cannot be the final result
+ _validatedAttestation.emit(null)
+ _remainingAttempts.value = RemainingAttempts()
+ }
+ }
+ }
+}
+
+private fun Context.asBadCredentialErrorMessage(prompt: BiometricPromptRequest?): String =
+ asBadCredentialErrorMessage(
+ if (prompt != null) prompt::class else BiometricPromptRequest.Credential.Password::class
+ )
+
+private fun <T : BiometricPromptRequest> Context.asBadCredentialErrorMessage(
+ clazz: KClass<T>
+): String =
+ getString(
+ when (clazz) {
+ BiometricPromptRequest.Credential.Pin::class -> R.string.biometric_dialog_wrong_pin
+ BiometricPromptRequest.Credential.Password::class ->
+ R.string.biometric_dialog_wrong_password
+ BiometricPromptRequest.Credential.Pattern::class ->
+ R.string.biometric_dialog_wrong_pattern
+ else -> R.string.biometric_dialog_wrong_password
+ }
+ )
+
+private fun Context.asLockIcon(userId: Int): Drawable {
+ val id =
+ if (Utils.isManagedProfile(this, userId)) {
+ R.drawable.auth_dialog_enterprise
+ } else {
+ R.drawable.auth_dialog_lock
+ }
+ return resources.getDrawable(id, theme)
+}
+
+private class BiometricPromptHeaderViewModelImpl(
+ val request: BiometricPromptRequest.Credential,
+ override val user: UserHandle,
+ override val title: String,
+ override val subtitle: String,
+ override val description: String,
+ override val icon: Drawable,
+) : HeaderViewModel
+
+private fun HeaderViewModel.asRequest(): BiometricPromptRequest.Credential =
+ (this as BiometricPromptHeaderViewModelImpl).request
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt
new file mode 100644
index 000000000000..ba23f1cfa22d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/HeaderViewModel.kt
@@ -0,0 +1,13 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+
+/** View model for the top-level header / info area of BiometricPrompt. */
+interface HeaderViewModel {
+ val user: UserHandle
+ val title: String
+ val subtitle: String
+ val description: String
+ val icon: Drawable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt
new file mode 100644
index 000000000000..0f221734cb44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/RemainingAttempts.kt
@@ -0,0 +1,4 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+/** Metadata about the number of credential attempts the user has left [remaining], if known. */
+data class RemainingAttempts(val remaining: Int? = null, val message: String = "")
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7e31626983e7..6db562107357 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -48,6 +48,7 @@ import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.notetask.NoteTaskModule;
import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
@@ -83,6 +84,7 @@ import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.statusbar.window.StatusBarWindowModule;
import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
+import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.unfold.SysUIUnfoldModule;
import com.android.systemui.user.UserModule;
@@ -149,9 +151,11 @@ import dagger.Provides;
SysUIConcurrencyModule.class,
SysUIUnfoldModule.class,
TelephonyRepositoryModule.class,
+ TemporaryDisplayModule.class,
TunerModule.class,
UserModule.class,
UtilModule.class,
+ NoteTaskModule.class,
WalletModule.class
},
subcomponents = {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
index 29bb2f42cca5..41f557850f88 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/Complication.java
@@ -164,7 +164,8 @@ public interface Complication {
COMPLICATION_TYPE_AIR_QUALITY,
COMPLICATION_TYPE_CAST_INFO,
COMPLICATION_TYPE_HOME_CONTROLS,
- COMPLICATION_TYPE_SMARTSPACE
+ COMPLICATION_TYPE_SMARTSPACE,
+ COMPLICATION_TYPE_MEDIA_ENTRY
})
@Retention(RetentionPolicy.SOURCE)
@interface ComplicationType {}
@@ -177,6 +178,7 @@ public interface Complication {
int COMPLICATION_TYPE_CAST_INFO = 1 << 4;
int COMPLICATION_TYPE_HOME_CONTROLS = 1 << 5;
int COMPLICATION_TYPE_SMARTSPACE = 1 << 6;
+ int COMPLICATION_TYPE_MEDIA_ENTRY = 1 << 7;
/**
* The {@link Host} interface specifies a way a {@link Complication} to communicate with its
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
index 75a97de10e7e..18aacd21bd12 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationUtils.java
@@ -20,6 +20,7 @@ import static com.android.systemui.dreams.complication.Complication.COMPLICATION
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_NONE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
@@ -54,6 +55,8 @@ public class ComplicationUtils {
return COMPLICATION_TYPE_HOME_CONTROLS;
case DreamBackend.COMPLICATION_TYPE_SMARTSPACE:
return COMPLICATION_TYPE_SMARTSPACE;
+ case DreamBackend.COMPLICATION_TYPE_MEDIA_ENTRY:
+ return COMPLICATION_TYPE_MEDIA_ENTRY;
default:
return COMPLICATION_TYPE_NONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
index 1166c2fc1120..deff0608bedd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamMediaEntryComplication.java
@@ -55,6 +55,11 @@ public class DreamMediaEntryComplication implements Complication {
return mComponentFactory.create().getViewHolder();
}
+ @Override
+ public int getRequiredTypeAvailability() {
+ return COMPLICATION_TYPE_MEDIA_ENTRY;
+ }
+
/**
* Contains values/logic associated with the dream complication view.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 3adeeac2e4d4..1f061e9ff1c6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -21,6 +21,7 @@ import static com.android.systemui.flags.FlagManager.ACTION_SET_FLAG;
import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
import static com.android.systemui.flags.FlagManager.EXTRA_ID;
import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
import static java.util.Objects.requireNonNull;
@@ -59,20 +60,20 @@ import javax.inject.Named;
*
* Flags can be set (or unset) via the following adb command:
*
- * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
+ * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
*
- * Alternatively, you can change flags via a broadcast intent:
+ * Alternatively, you can change flags via a broadcast intent:
*
- * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
*
* To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
*/
@SysUISingleton
public class FeatureFlagsDebug implements FeatureFlags {
static final String TAG = "SysUIFlags";
- static final String ALL_FLAGS = "all_flags";
private final FlagManager mFlagManager;
+ private final Context mContext;
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
@@ -83,6 +84,14 @@ public class FeatureFlagsDebug implements FeatureFlags {
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
private final Restarter mRestarter;
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsDebug(
FlagManager flagManager,
@@ -93,23 +102,28 @@ public class FeatureFlagsDebug implements FeatureFlags {
DeviceConfigProxy deviceConfigProxy,
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
- Restarter barService) {
+ Restarter restarter) {
mFlagManager = flagManager;
+ mContext = context;
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
- mRestarter = barService;
+ mRestarter = restarter;
+ }
+ /** Call after construction to setup listeners. */
+ void init() {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_SET_FLAG);
filter.addAction(ACTION_GET_FLAGS);
- flagManager.setOnSettingsChangedAction(this::restartSystemUI);
- flagManager.setClearCacheAction(this::removeFromCache);
- context.registerReceiver(mReceiver, filter, null, null,
+ mFlagManager.setOnSettingsChangedAction(this::restartSystemUI);
+ mFlagManager.setClearCacheAction(this::removeFromCache);
+ mContext.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
@@ -196,7 +210,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
return mStringFlagCache.get(id);
}
- /** Specific override for Boolean flags that checks against the teamfood list.*/
+ /** Specific override for Boolean flags that checks against the teamfood list. */
private boolean readFlagValue(int id, boolean defaultValue) {
Boolean result = readBooleanFlagOverride(id);
boolean hasServerOverride = mServerFlagReader.hasOverride(id);
@@ -273,6 +287,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
}
+
/** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
private void eraseInternal(int id) {
// We can't actually "erase" things from sysprops, but we can set them to empty!
@@ -358,7 +373,7 @@ public class FeatureFlagsDebug implements FeatureFlags {
}
}
- Bundle extras = getResultExtras(true);
+ Bundle extras = getResultExtras(true);
if (extras != null) {
extras.putParcelableArrayList(EXTRA_FLAGS, pFlags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 560dcbd78c42..62713348c789 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -31,7 +31,7 @@ constructor(
dumpManager: DumpManager,
private val commandRegistry: CommandRegistry,
private val flagCommand: FlagCommand,
- featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlagsDebug
) : CoreStartable {
init {
@@ -41,6 +41,7 @@ constructor(
}
override fun start() {
+ featureFlags.init()
commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 40a8a1a9ef01..30cad5faa2de 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import static java.util.Objects.requireNonNull;
import android.content.res.Resources;
@@ -34,6 +36,7 @@ import java.io.PrintWriter;
import java.util.Map;
import javax.inject.Inject;
+import javax.inject.Named;
/**
* Default implementation of the a Flag manager that returns default values for release builds
@@ -49,26 +52,47 @@ public class FeatureFlagsRelease implements FeatureFlags {
private final SystemPropertiesHelper mSystemProperties;
private final DeviceConfigProxy mDeviceConfigProxy;
private final ServerFlagReader mServerFlagReader;
+ private final Restarter mRestarter;
+ private final Map<Integer, Flag<?>> mAllFlags;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
+ private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+ new ServerFlagReader.ChangeListener() {
+ @Override
+ public void onChange() {
+ mRestarter.restart();
+ }
+ };
+
@Inject
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
DeviceConfigProxy deviceConfigProxy,
- ServerFlagReader serverFlagReader) {
+ ServerFlagReader serverFlagReader,
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+ Restarter restarter) {
mResources = resources;
mSystemProperties = systemProperties;
mDeviceConfigProxy = deviceConfigProxy;
mServerFlagReader = serverFlagReader;
+ mAllFlags = allFlags;
+ mRestarter = restarter;
+ }
+
+ /** Call after construction to setup listeners. */
+ void init() {
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
- public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {}
+ public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {
+ }
@Override
- public void removeListener(@NonNull Listener listener) {}
+ public void removeListener(@NonNull Listener listener) {
+ }
@Override
public boolean isEnabled(@NotNull UnreleasedFlag flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 4d254313a57b..1e93c0b7002d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
import androidx.annotation.NonNull;
import com.android.systemui.statusbar.commandline.Command;
@@ -42,7 +44,7 @@ public class FlagCommand implements Command {
@Inject
FlagCommand(
FeatureFlagsDebug featureFlags,
- @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+ @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
) {
mFeatureFlags = featureFlags;
mAllFlags = allFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 9ca6dd63ec40..fa499687fa21 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.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.
@@ -57,7 +57,7 @@ object Flags {
val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
// TODO(b/254512425): Tracking Bug
- val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = false)
+ val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
@@ -118,6 +118,16 @@ object Flags {
*/
@JvmField val STEP_CLOCK_ANIMATION = UnreleasedFlag(212)
+ /**
+ * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
+ * will occur in stages. This is one stage of many to come.
+ */
+ @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true)
+
+ @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214)
+
+ @JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = ReleasedFlag(300)
@@ -132,7 +142,7 @@ object Flags {
@Deprecated("Not needed anymore") val NEW_USER_SWITCHER = ReleasedFlag(500)
// TODO(b/254512321): Tracking Bug
- @JvmField val COMBINED_QS_HEADERS = ReleasedFlag(501, teamfood = true)
+ @JvmField val COMBINED_QS_HEADERS = UnreleasedFlag(501, teamfood = true)
val PEOPLE_TILE = ResourceBooleanFlag(502, R.bool.flag_conversations)
@JvmField
val QS_USER_DETAIL_SHORTCUT =
@@ -142,7 +152,7 @@ object Flags {
@Deprecated("Not needed anymore") val NEW_FOOTER = ReleasedFlag(504)
// TODO(b/254512747): Tracking Bug
- val NEW_HEADER = ReleasedFlag(505, teamfood = true)
+ val NEW_HEADER = UnreleasedFlag(505, teamfood = true)
// TODO(b/254512383): Tracking Bug
@JvmField
@@ -152,13 +162,13 @@ object Flags {
// TODO(b/254512678): Tracking Bug
@JvmField val NEW_FOOTER_ACTIONS = ReleasedFlag(507)
+ // TODO(b/244064524): Tracking Bug
+ @JvmField val QS_SECONDARY_DATA_SUB_INFO = UnreleasedFlag(508, teamfood = true)
+
// 600- status bar
// TODO(b/254513246): Tracking Bug
val STATUS_BAR_USER_SWITCHER = ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip)
- // TODO(b/254513025): Tracking Bug
- val STATUS_BAR_LETTERBOX_APPEARANCE = ReleasedFlag(603, teamfood = false)
-
// TODO(b/254512623): Tracking Bug
@Deprecated("Replaced by mobile and wifi specific flags.")
val NEW_STATUS_BAR_PIPELINE_BACKEND = UnreleasedFlag(604, teamfood = false)
@@ -190,7 +200,7 @@ object Flags {
// 802 - wallpaper rendering
// TODO(b/254512923): Tracking Bug
- @JvmField val USE_CANVAS_RENDERER = ReleasedFlag(802)
+ @JvmField val USE_CANVAS_RENDERER = UnreleasedFlag(802, teamfood = true)
// 803 - screen contents translation
// TODO(b/254513187): Tracking Bug
@@ -224,15 +234,9 @@ object Flags {
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
- // TODO(b/254512444): Tracking Bug
- @JvmField val DOCK_SETUP_ENABLED = ReleasedFlag(1001)
-
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
- // TODO(b/254512525): Tracking Bug
- @JvmField val REFACTORED_DOCK_SETUP = ReleasedFlag(1003, teamfood = true)
-
// 1100 - windowing
@Keep
val WM_ENABLE_SHELL_TRANSITIONS =
@@ -314,7 +318,7 @@ object Flags {
// 1500 - chooser
// TODO(b/254512507): Tracking Bug
- val CHOOSER_UNBUNDLED = UnreleasedFlag(1500)
+ val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, true)
// 1600 - accessibility
@JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
@@ -325,6 +329,9 @@ object Flags {
// 1800 - shade container
@JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
+ // 1900 - note task
+ @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
new file mode 100644
index 000000000000..e1f4944741a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.flags
+
+import com.android.internal.statusbar.IStatusBarService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+/** Module containing shared code for all FeatureFlag implementations. */
+@Module
+interface FlagsCommonModule {
+ companion object {
+ const val ALL_FLAGS = "all_flags"
+
+ @JvmStatic
+ @Provides
+ @Named(ALL_FLAGS)
+ fun providesAllFlags(): Map<Int, Flag<*>> {
+ return Flags.collectFlags()
+ }
+
+ @JvmStatic
+ @Provides
+ fun providesRestarter(barService: IStatusBarService): Restarter {
+ return object : Restarter {
+ override fun restart() {
+ barService.restart()
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index fc5b9f4eea05..694fa01bb4a5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -16,9 +16,13 @@
package com.android.systemui.flags
+import android.provider.DeviceConfig
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.DeviceConfigProxy
-import dagger.Binds
import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
import javax.inject.Inject
interface ServerFlagReader {
@@ -27,40 +31,99 @@ interface ServerFlagReader {
/** Returns any stored server-side setting or the default if not set. */
fun readServerOverride(flagId: Int, default: Boolean): Boolean
+
+ /** Register a listener for changes to any of the passed in flags. */
+ fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
+
+ interface ChangeListener {
+ fun onChange()
+ }
}
class ServerFlagReaderImpl @Inject constructor(
- private val deviceConfig: DeviceConfigProxy
+ private val namespace: String,
+ private val deviceConfig: DeviceConfigProxy,
+ @Background private val executor: Executor
) : ServerFlagReader {
+
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
+
+ private val onPropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener {
+ override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ if (properties.namespace != namespace) {
+ return
+ }
+
+ for ((listener, flags) in listeners) {
+ propLoop@ for (propName in properties.keyset) {
+ for (flag in flags) {
+ if (propName == getServerOverrideName(flag.id)) {
+ listener.onChange()
+ break@propLoop
+ }
+ }
+ }
+ }
+ }
+ }
+
override fun hasOverride(flagId: Int): Boolean =
deviceConfig.getProperty(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId)
) != null
override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
return deviceConfig.getBoolean(
- SYSUI_NAMESPACE,
+ namespace,
getServerOverrideName(flagId),
default
)
}
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ if (listeners.isEmpty()) {
+ deviceConfig.addOnPropertiesChangedListener(
+ namespace,
+ executor,
+ onPropertiesChangedListener
+ )
+ }
+ listeners.add(Pair(listener, flags))
+ }
+
private fun getServerOverrideName(flagId: Int): String {
return "flag_override_$flagId"
}
}
-private val SYSUI_NAMESPACE = "systemui"
-
@Module
interface ServerFlagReaderModule {
- @Binds
- fun bindsReader(impl: ServerFlagReaderImpl): ServerFlagReader
+ companion object {
+ private val SYSUI_NAMESPACE = "systemui"
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun bindsReader(
+ deviceConfig: DeviceConfigProxy,
+ @Background executor: Executor
+ ): ServerFlagReader {
+ return ServerFlagReaderImpl(
+ SYSUI_NAMESPACE, deviceConfig, executor
+ )
+ }
+ }
}
class ServerFlagReaderFake : ServerFlagReader {
private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
+ private val listeners =
+ mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
override fun hasOverride(flagId: Int): Boolean {
return flagMap.containsKey(flagId)
@@ -72,9 +135,24 @@ class ServerFlagReaderFake : ServerFlagReader {
fun setFlagValue(flagId: Int, value: Boolean) {
flagMap.put(flagId, value)
+
+ for ((listener, flags) in listeners) {
+ flagLoop@ for (flag in flags) {
+ if (flagId == flag.id) {
+ listener.onChange()
+ break@flagLoop
+ }
+ }
+ }
}
fun eraseFlag(flagId: Int) {
flagMap.remove(flagId)
}
+
+ override fun listenForChanges(
+ flags: Collection<Flag<*>>,
+ listener: ServerFlagReader.ChangeListener
+ ) {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 1c6cec22ffca..021431399ab6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -518,7 +518,7 @@ public class KeyguardService extends Service {
@PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
checkPermission();
- mKeyguardViewMediator.onStartedWakingUp(cameraGestureTriggered);
+ mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
mKeyguardLifecyclesDispatcher.dispatch(
KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason);
Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0d74dc850dda..41abb62f05cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -91,6 +91,7 @@ import android.view.animation.AnimationUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -322,6 +323,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// true if the keyguard is hidden by another window
private boolean mOccluded = false;
+ /**
+ * Whether the {@link #mOccludeAnimationController} is currently playing the occlusion
+ * animation.
+ */
+ private boolean mOccludeAnimationPlaying = false;
+
private boolean mWakeAndUnlocking = false;
/**
@@ -335,12 +342,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
*/
private int mDelayedProfileShowingSequence;
- /**
- * If the user has disabled the keyguard, then requests to exit, this is
- * how we'll ultimately let them know whether it was successful. We use this
- * var being non-null as an indicator that there is an in progress request.
- */
- private IKeyguardExitCallback mExitSecureCallback;
private final DismissCallbackRegistry mDismissCallbackRegistry;
// the properties of the keyguard
@@ -837,15 +838,22 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
/**
* Animation launch controller for activities that occlude the keyguard.
*/
- private final ActivityLaunchAnimator.Controller mOccludeAnimationController =
+ @VisibleForTesting
+ final ActivityLaunchAnimator.Controller mOccludeAnimationController =
new ActivityLaunchAnimator.Controller() {
@Override
- public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {}
+ public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+ mOccludeAnimationPlaying = true;
+ }
@Override
public void onLaunchAnimationCancelled(@Nullable Boolean newKeyguardOccludedState) {
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
+ mOccludeAnimationPlaying = false;
+
+ // Ensure keyguard state is set correctly if we're cancelled.
+ mCentralSurfaces.updateIsKeyguard();
}
@Override
@@ -854,6 +862,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mCentralSurfaces.instantCollapseNotificationPanel();
}
+ mOccludeAnimationPlaying = false;
+
+ // Hide the keyguard now that we're done launching the occluding activity over
+ // it.
+ mCentralSurfaces.updateIsKeyguard();
+
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION);
}
@@ -1322,18 +1336,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
|| !mLockPatternUtils.isSecure(currentUser);
long timeout = getLockTimeout(KeyguardUpdateMonitor.getCurrentUser());
mLockLater = false;
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
- try {
- mExitSecureCallback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
- mExitSecureCallback = null;
- if (!mExternallyEnabled) {
- hideLocked();
- }
- } else if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
+ if (mShowing && !mKeyguardStateController.isKeyguardGoingAway()) {
// If we are going to sleep but the keyguard is showing (and will continue to be
// showing, not in the process of going away) then reset its state. Otherwise, let
// this fall through and explicitly re-lock the keyguard.
@@ -1575,7 +1578,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
/**
* It will let us know when the device is waking up.
*/
- public void onStartedWakingUp(boolean cameraGestureTriggered) {
+ public void onStartedWakingUp(@PowerManager.WakeReason int pmWakeReason,
+ boolean cameraGestureTriggered) {
Trace.beginSection("KeyguardViewMediator#onStartedWakingUp");
// TODO: Rename all screen off/on references to interactive/sleeping
@@ -1590,7 +1594,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (DEBUG) Log.d(TAG, "onStartedWakingUp, seq = " + mDelayedShowingSequence);
notifyStartedWakingUp();
}
- mUpdateMonitor.dispatchStartedWakingUp();
+ mUpdateMonitor.dispatchStartedWakingUp(pmWakeReason);
maybeSendUserPresentBroadcast();
Trace.endSection();
}
@@ -1652,13 +1656,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mExternallyEnabled = enabled;
if (!enabled && mShowing) {
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "in process of verifyUnlock request, ignoring");
- // we're in the process of handling a request to verify the user
- // can get past the keyguard. ignore extraneous requests to disable / re-enable
- return;
- }
-
// hiding keyguard that is showing, remember to reshow later
if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ "disabling status bar expansion");
@@ -1672,33 +1669,23 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mNeedToReshowWhenReenabled = false;
updateInputRestrictedLocked();
- if (mExitSecureCallback != null) {
- if (DEBUG) Log.d(TAG, "onKeyguardExitResult(false), resetting");
+ showLocked(null);
+
+ // block until we know the keyguard is done drawing (and post a message
+ // to unblock us after a timeout, so we don't risk blocking too long
+ // and causing an ANR).
+ mWaitingUntilKeyguardVisible = true;
+ mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING,
+ KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
+ if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
+ while (mWaitingUntilKeyguardVisible) {
try {
- mExitSecureCallback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
- mExitSecureCallback = null;
- resetStateLocked();
- } else {
- showLocked(null);
-
- // block until we know the keyguard is done drawing (and post a message
- // to unblock us after a timeout, so we don't risk blocking too long
- // and causing an ANR).
- mWaitingUntilKeyguardVisible = true;
- mHandler.sendEmptyMessageDelayed(KEYGUARD_DONE_DRAWING, KEYGUARD_DONE_DRAWING_TIMEOUT_MS);
- if (DEBUG) Log.d(TAG, "waiting until mWaitingUntilKeyguardVisible is false");
- while (mWaitingUntilKeyguardVisible) {
- try {
- wait();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
- if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
}
+ if (DEBUG) Log.d(TAG, "done waiting for mWaitingUntilKeyguardVisible");
}
}
}
@@ -1728,13 +1715,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
}
- } else if (mExitSecureCallback != null) {
- // already in progress with someone else
- try {
- callback.onKeyguardExitResult(false);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
- }
} else if (!isSecure()) {
// Keyguard is not secure, no need to do anything, and we don't need to reshow
@@ -1768,6 +1748,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
return mShowing && !mOccluded;
}
+ public boolean isOccludeAnimationPlaying() {
+ return mOccludeAnimationPlaying;
+ }
+
/**
* Notify us when the keyguard is occluded by another window
*/
@@ -2265,21 +2249,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
return;
}
setPendingLock(false); // user may have authenticated during the screen off animation
- if (mExitSecureCallback != null) {
- try {
- mExitSecureCallback.onKeyguardExitResult(true /* authenciated */);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onKeyguardExitResult()", e);
- }
-
- mExitSecureCallback = null;
-
- // after successfully exiting securely, no need to reshow
- // the keyguard when they've released the lock
- mExternallyEnabled = true;
- mNeedToReshowWhenReenabled = false;
- updateInputRestricted();
- }
handleHide();
mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser);
@@ -3087,7 +3056,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
pw.print(" mInputRestricted: "); pw.println(mInputRestricted);
pw.print(" mOccluded: "); pw.println(mOccluded);
pw.print(" mDelayedShowingSequence: "); pw.println(mDelayedShowingSequence);
- pw.print(" mExitSecureCallback: "); pw.println(mExitSecureCallback);
pw.print(" mDeviceInteractive: "); pw.println(mDeviceInteractive);
pw.print(" mGoingToSleep: "); pw.println(mGoingToSleep);
pw.print(" mHiding: "); pw.println(mHiding);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
new file mode 100644
index 000000000000..a069582f6692
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.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.data.quickaffordance
+
+/**
+ * Unique identifier keys for all known built-in quick affordances.
+ *
+ * Please ensure uniqueness by never associating more than one class with each key.
+ */
+object BuiltInKeyguardQuickAffordanceKeys {
+ // Please keep alphabetical order of const names to simplify future maintenance.
+ const val HOME_CONTROLS = "home"
+ const val QR_CODE_SCANNER = "qr_code_scanner"
+ const val QUICK_ACCESS_WALLET = "wallet"
+ // Please keep alphabetical order of const names to simplify future maintenance.
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 83842602cbee..c600e13cc2dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
import android.content.Intent
@@ -51,19 +51,21 @@ constructor(
private val appContext = context.applicationContext
- override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
if (canShowWhileLocked) {
stateInternal(component.getControlsListingController().getOrNull())
} else {
- flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
}
- override fun onQuickAffordanceClicked(
+ override fun onTriggered(
expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent =
Intent(appContext, ControlsActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -77,9 +79,9 @@ constructor(
private fun stateInternal(
listingController: ControlsListingController?,
- ): Flow<KeyguardQuickAffordanceConfig.State> {
+ ): Flow<KeyguardQuickAffordanceConfig.LockScreenState> {
if (listingController == null) {
- return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+ return flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
}
return conflatedCallbackFlow {
@@ -114,7 +116,7 @@ constructor(
hasServiceInfos: Boolean,
visibility: ControlsComponent.Visibility,
@DrawableRes iconResourceId: Int?,
- ): KeyguardQuickAffordanceConfig.State {
+ ): KeyguardQuickAffordanceConfig.LockScreenState {
return if (
isFeatureEnabled &&
hasFavorites &&
@@ -122,7 +124,7 @@ constructor(
iconResourceId != null &&
visibility == ControlsComponent.Visibility.AVAILABLE
) {
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon =
Icon.Resource(
res = iconResourceId,
@@ -133,7 +135,7 @@ constructor(
),
)
} else {
- KeyguardQuickAffordanceConfig.State.Hidden
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..0a8090bb11ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.quickaffordance
+
+import android.content.Intent
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import kotlinx.coroutines.flow.Flow
+
+/** Defines interface that can act as data source for a single quick affordance model. */
+interface KeyguardQuickAffordanceConfig {
+
+ /** Unique identifier for this quick affordance. It must be globally unique. */
+ val key: String
+
+ /**
+ * The ever-changing state of the affordance.
+ *
+ * Used to populate the lock screen.
+ */
+ val lockScreenState: Flow<LockScreenState>
+
+ /**
+ * Notifies that the affordance was clicked by the user.
+ *
+ * @param expandable An [Expandable] to use when animating dialogs or activities
+ * @return An [OnTriggeredResult] telling the caller what to do next
+ */
+ fun onTriggered(expandable: Expandable?): OnTriggeredResult
+
+ /**
+ * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
+ * button on the lock-screen).
+ */
+ sealed class LockScreenState {
+
+ /** No affordance should show up. */
+ object Hidden : LockScreenState()
+
+ /** An affordance is visible. */
+ data class Visible(
+ /** An icon for the affordance. */
+ val icon: Icon,
+ /** The activation state of the affordance. */
+ val activationState: ActivationState = ActivationState.NotSupported,
+ ) : LockScreenState()
+ }
+
+ sealed class OnTriggeredResult {
+ /**
+ * Returning this as a result from the [onTriggered] method means that the implementation
+ * has taken care of the action, the system will do nothing.
+ */
+ object Handled : OnTriggeredResult()
+
+ /**
+ * Returning this as a result from the [onTriggered] method means that the implementation
+ * has _not_ taken care of the action and the system should start an activity using the
+ * given [Intent].
+ */
+ data class StartActivity(
+ val intent: Intent,
+ val canShowWhileLocked: Boolean,
+ ) : OnTriggeredResult()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..d620b2a1654c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.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.systemui.keyguard.data.quickaffordance
+
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+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.qrcodescanner.controller.QRCodeScannerController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** QR code scanner quick affordance data source. */
+@SysUISingleton
+class QrCodeScannerKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val controller: QRCodeScannerController,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : QRCodeScannerController.Callback {
+ override fun onQRCodeScannerActivityChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ override fun onQRCodeScannerPreferenceChanged() {
+ trySendWithFailureLogging(state(), TAG)
+ }
+ }
+
+ controller.addCallback(callback)
+ controller.registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ // Registering does not push an initial update.
+ trySendWithFailureLogging(state(), "initial state", TAG)
+
+ awaitClose {
+ controller.unregisterQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+ )
+ controller.removeCallback(callback)
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?,
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = controller.intent,
+ canShowWhileLocked = true,
+ )
+ }
+
+ private fun state(): KeyguardQuickAffordanceConfig.LockScreenState {
+ return if (controller.isEnabledForLockScreenButton) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
+ res = R.drawable.ic_qr_code_scanner,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_qr_code_scanner_button,
+ ),
+ ),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..be57a323b5d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -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.systemui.keyguard.data.quickaffordance
+
+import android.graphics.drawable.Drawable
+import android.service.quickaccesswallet.GetWalletCardsError
+import android.service.quickaccesswallet.GetWalletCardsResponse
+import android.service.quickaccesswallet.QuickAccessWalletClient
+import android.util.Log
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+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.plugins.ActivityStarter
+import com.android.systemui.wallet.controller.QuickAccessWalletController
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Quick access wallet quick affordance data source. */
+@SysUISingleton
+class QuickAccessWalletKeyguardQuickAffordanceConfig
+@Inject
+constructor(
+ private val walletController: QuickAccessWalletController,
+ private val activityStarter: ActivityStarter,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ conflatedCallbackFlow {
+ val callback =
+ object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+ override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+ trySendWithFailureLogging(
+ state(
+ isFeatureEnabled = walletController.isWalletEnabled,
+ hasCard = response?.walletCards?.isNotEmpty() == true,
+ tileIcon = walletController.walletClient.tileIcon,
+ ),
+ TAG,
+ )
+ }
+
+ override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+ Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+ trySendWithFailureLogging(
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
+ TAG,
+ )
+ }
+ }
+
+ walletController.setupWalletChangeObservers(
+ callback,
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ walletController.updateWalletPreference()
+ walletController.queryWalletCards(callback)
+
+ awaitClose {
+ walletController.unregisterWalletChangeObservers(
+ QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+ QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+ )
+ }
+ }
+
+ override fun onTriggered(
+ expandable: Expandable?,
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ walletController.startQuickAccessUiIntent(
+ activityStarter,
+ expandable?.activityLaunchController(),
+ /* hasCard= */ true,
+ )
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+
+ private fun state(
+ isFeatureEnabled: Boolean,
+ hasCard: Boolean,
+ tileIcon: Drawable?,
+ ): KeyguardQuickAffordanceConfig.LockScreenState {
+ return if (isFeatureEnabled && hasCard && tileIcon != null) {
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Loaded(
+ drawable = tileIcon,
+ contentDescription =
+ ContentDescription.Resource(
+ res = R.string.accessibility_wallet_button,
+ ),
+ ),
+ )
+ } else {
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
+ }
+ }
+
+ companion object {
+ private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
+ }
+}
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 e8532ecfdc37..ab25597b077c 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
@@ -20,6 +20,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.annotation.FloatRange
+import android.os.Trace
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -157,12 +158,36 @@ class KeyguardTransitionRepository @Inject constructor() {
value: Float,
transitionState: TransitionState
) {
+ trace(info, transitionState)
+
if (transitionState == TransitionState.FINISHED) {
currentTransitionInfo = null
}
_transitions.value = TransitionStep(info.from, info.to, value, transitionState)
}
+ private fun trace(info: TransitionInfo, transitionState: TransitionState) {
+ if (
+ transitionState != TransitionState.STARTED &&
+ transitionState != TransitionState.FINISHED
+ ) {
+ return
+ }
+ val traceName =
+ "Transition: ${info.from} -> ${info.to} " +
+ if (info.animator == null) {
+ "(manual)"
+ } else {
+ ""
+ }
+ val traceCookie = traceName.hashCode()
+ if (transitionState == TransitionState.STARTED) {
+ Trace.beginAsyncSection(traceName, traceCookie)
+ } else if (transitionState == TransitionState.FINISHED) {
+ Trace.endAsyncSection(traceName, traceCookie)
+ }
+ }
+
companion object {
private const val TAG = "KeyguardTransitionRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index f663b0dd23cd..13d97aaf28da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -21,15 +21,14 @@ import android.content.Intent
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
@@ -63,25 +62,25 @@ constructor(
}
/**
- * Notifies that a quick affordance has been clicked by the user.
+ * Notifies that a quick affordance has been "triggered" (clicked) by the user.
*
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
* the affordance that was clicked
* @param expandable An optional [Expandable] for the activity- or dialog-launch animation
*/
- fun onQuickAffordanceClicked(
- configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ fun onQuickAffordanceTriggered(
+ configKey: String,
expandable: Expandable?,
) {
- @Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
- when (val result = config.onQuickAffordanceClicked(expandable)) {
- is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+ @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
+ when (val result = config.onTriggered(expandable)) {
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
launchQuickAffordance(
intent = result.intent,
canShowWhileLocked = result.canShowWhileLocked,
expandable = expandable,
)
- is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+ is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit
}
}
@@ -95,16 +94,20 @@ constructor(
// value and avoid subtle bugs where the downstream isn't receiving any values
// because one config implementation is not emitting an initial value. For example,
// see b/244296596.
- config.state.onStart { emit(KeyguardQuickAffordanceConfig.State.Hidden) }
+ config.lockScreenState.onStart {
+ emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+ }
}
) { states ->
- val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+ val index =
+ states.indexOfFirst { it is KeyguardQuickAffordanceConfig.LockScreenState.Visible }
if (index != -1) {
- val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+ val visibleState =
+ states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
KeyguardQuickAffordanceModel.Visible(
- configKey = configs[index]::class,
+ configKey = configs[index].key,
icon = visibleState.icon,
- toggle = visibleState.toggle,
+ activationState = visibleState.activationState,
)
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 59bb22786917..7409aec57b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -24,6 +24,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
/** Encapsulates business-logic related to the keyguard transitions. */
@SysUISingleton
@@ -34,4 +36,17 @@ constructor(
) {
/** AOD->LOCKSCREEN transition information. */
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
+
+ /** LOCKSCREEN->AOD transition information. */
+ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
+
+ /**
+ * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
+ * Lockscreen (0f).
+ */
+ val dozeAmountTransition: Flow<TransitionStep> =
+ merge(
+ aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
+ lockscreenToAodTransition,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index e56b25967910..32560af6af00 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -18,9 +18,7 @@
package com.android.systemui.keyguard.domain.model
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
-import kotlin.reflect.KClass
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
/**
* Models a "quick affordance" in the keyguard bottom area (for example, a button on the
@@ -33,10 +31,10 @@ sealed class KeyguardQuickAffordanceModel {
/** A affordance is visible. */
data class Visible(
/** Identifier for the affordance this is modeling. */
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
/** An icon for the affordance. */
val icon: Icon,
- /** The toggle state for the affordance. */
- val toggle: KeyguardQuickAffordanceToggleState,
+ /** The activation state of the affordance. */
+ val activationState: ActivationState,
) : KeyguardQuickAffordanceModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 95027d00c46c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,69 +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.quickaffordance
-
-import android.content.Intent
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
-import kotlinx.coroutines.flow.Flow
-
-/** Defines interface that can act as data source for a single quick affordance model. */
-interface KeyguardQuickAffordanceConfig {
-
- val state: Flow<State>
-
- fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
-
- /**
- * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
- * button on the lock-screen).
- */
- sealed class State {
-
- /** No affordance should show up. */
- object Hidden : State()
-
- /** An affordance is visible. */
- data class Visible(
- /** An icon for the affordance. */
- val icon: Icon,
- /** The toggle state for the affordance. */
- val toggle: KeyguardQuickAffordanceToggleState =
- KeyguardQuickAffordanceToggleState.NotSupported,
- ) : State()
- }
-
- sealed class OnClickedResult {
- /**
- * Returning this as a result from the [onQuickAffordanceClicked] method means that the
- * implementation has taken care of the click, the system will do nothing.
- */
- object Handled : OnClickedResult()
-
- /**
- * Returning this as a result from the [onQuickAffordanceClicked] method means that the
- * implementation has _not_ taken care of the click and the system should start an activity
- * using the given [Intent].
- */
- data class StartActivity(
- val intent: Intent,
- val canShowWhileLocked: Boolean,
- ) : OnClickedResult()
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
index 94024d4a0ace..b48acb65849e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.quickaffordance
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import dagger.Binds
import dagger.Module
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
index ad40ee7a0183..8526ada69569 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/KeyguardQuickAffordanceRegistry.kt
@@ -17,14 +17,17 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import javax.inject.Inject
-import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */
interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
fun getAll(position: KeyguardQuickAffordancePosition): List<T>
- fun get(configClass: KClass<out T>): T
+ fun get(key: String): T
}
class KeyguardQuickAffordanceRegistryImpl
@@ -46,8 +49,8 @@ constructor(
qrCodeScanner,
),
)
- private val configByClass =
- configsByPosition.values.flatten().associateBy { config -> config::class }
+ private val configByKey =
+ configsByPosition.values.flatten().associateBy { config -> config.key }
override fun getAll(
position: KeyguardQuickAffordancePosition,
@@ -56,8 +59,8 @@ constructor(
}
override fun get(
- configClass: KClass<out KeyguardQuickAffordanceConfig>
+ key: String,
): KeyguardQuickAffordanceConfig {
- return configByClass.getValue(configClass)
+ return configByKey.getValue(key)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index 502a6070a422..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,97 +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.quickaffordance
-
-import com.android.systemui.R
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-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.qrcodescanner.controller.QRCodeScannerController
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** QR code scanner quick affordance data source. */
-@SysUISingleton
-class QrCodeScannerKeyguardQuickAffordanceConfig
-@Inject
-constructor(
- private val controller: QRCodeScannerController,
-) : KeyguardQuickAffordanceConfig {
-
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
- val callback =
- object : QRCodeScannerController.Callback {
- override fun onQRCodeScannerActivityChanged() {
- trySendWithFailureLogging(state(), TAG)
- }
- override fun onQRCodeScannerPreferenceChanged() {
- trySendWithFailureLogging(state(), TAG)
- }
- }
-
- controller.addCallback(callback)
- controller.registerQRCodeScannerChangeObservers(
- QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
- QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
- )
- // Registering does not push an initial update.
- trySendWithFailureLogging(state(), "initial state", TAG)
-
- awaitClose {
- controller.unregisterQRCodeScannerChangeObservers(
- QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
- QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
- )
- controller.removeCallback(callback)
- }
- }
-
- override fun onQuickAffordanceClicked(
- expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
- intent = controller.intent,
- canShowWhileLocked = true,
- )
- }
-
- private fun state(): KeyguardQuickAffordanceConfig.State {
- return if (controller.isEnabledForLockScreenButton) {
- KeyguardQuickAffordanceConfig.State.Visible(
- icon =
- Icon.Resource(
- res = R.drawable.ic_qr_code_scanner,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_qr_code_scanner_button,
- ),
- ),
- )
- } else {
- KeyguardQuickAffordanceConfig.State.Hidden
- }
- }
-
- companion object {
- private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
deleted file mode 100644
index a24a0d62465f..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,121 +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.quickaffordance
-
-import android.graphics.drawable.Drawable
-import android.service.quickaccesswallet.GetWalletCardsError
-import android.service.quickaccesswallet.GetWalletCardsResponse
-import android.service.quickaccesswallet.QuickAccessWalletClient
-import android.util.Log
-import com.android.systemui.R
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-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.plugins.ActivityStarter
-import com.android.systemui.wallet.controller.QuickAccessWalletController
-import javax.inject.Inject
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
-
-/** Quick access wallet quick affordance data source. */
-@SysUISingleton
-class QuickAccessWalletKeyguardQuickAffordanceConfig
-@Inject
-constructor(
- private val walletController: QuickAccessWalletController,
- private val activityStarter: ActivityStarter,
-) : KeyguardQuickAffordanceConfig {
-
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
- val callback =
- object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
- override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
- trySendWithFailureLogging(
- state(
- isFeatureEnabled = walletController.isWalletEnabled,
- hasCard = response?.walletCards?.isNotEmpty() == true,
- tileIcon = walletController.walletClient.tileIcon,
- ),
- TAG,
- )
- }
-
- override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
- Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
- trySendWithFailureLogging(
- KeyguardQuickAffordanceConfig.State.Hidden,
- TAG,
- )
- }
- }
-
- walletController.setupWalletChangeObservers(
- callback,
- QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
- )
- walletController.updateWalletPreference()
- walletController.queryWalletCards(callback)
-
- awaitClose {
- walletController.unregisterWalletChangeObservers(
- QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
- QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
- )
- }
- }
-
- override fun onQuickAffordanceClicked(
- expandable: Expandable?,
- ): KeyguardQuickAffordanceConfig.OnClickedResult {
- walletController.startQuickAccessUiIntent(
- activityStarter,
- expandable?.activityLaunchController(),
- /* hasCard= */ true,
- )
- return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
- }
-
- private fun state(
- isFeatureEnabled: Boolean,
- hasCard: Boolean,
- tileIcon: Drawable?,
- ): KeyguardQuickAffordanceConfig.State {
- return if (isFeatureEnabled && hasCard && tileIcon != null) {
- KeyguardQuickAffordanceConfig.State.Visible(
- icon =
- Icon.Loaded(
- drawable = tileIcon,
- contentDescription =
- ContentDescription.Resource(
- res = R.string.accessibility_wallet_button,
- ),
- ),
- )
- } else {
- KeyguardQuickAffordanceConfig.State.Hidden
- }
- }
-
- companion object {
- private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
index 55d38a41849d..a68d190a3a0c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
@@ -17,12 +17,12 @@
package com.android.systemui.keyguard.shared.quickaffordance
-/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */
-sealed class KeyguardQuickAffordanceToggleState {
- /** Toggling is not supported. */
- object NotSupported : KeyguardQuickAffordanceToggleState()
+/** Enumerates all possible activation states for a quick affordance on the lock-screen. */
+sealed class ActivationState {
+ /** Activation is not supported. */
+ object NotSupported : ActivationState()
/** The quick affordance is on. */
- object On : KeyguardQuickAffordanceToggleState()
+ object Active : ActivationState()
/** The quick affordance is off. */
- object Off : KeyguardQuickAffordanceToggleState()
+ object Inactive : ActivationState()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index 581dafa33df7..a18b036c5189 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.keyguard.shared.quickaffordance
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 535ca7210244..b6b230441397 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -22,8 +22,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInterac
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -118,13 +118,13 @@ constructor(
animateReveal = animateReveal,
icon = icon,
onClicked = { parameters ->
- quickAffordanceInteractor.onQuickAffordanceClicked(
+ quickAffordanceInteractor.onQuickAffordanceTriggered(
configKey = parameters.configKey,
expandable = parameters.expandable,
)
},
isClickable = isClickable,
- isActivated = toggle is KeyguardQuickAffordanceToggleState.On,
+ isActivated = activationState is ActivationState.Active,
)
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index bf598ba85932..44f48f97b62e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -18,12 +18,10 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
+ val configKey: String? = null,
val isVisible: Boolean = false,
/** Whether to animate the transition of the quick affordance from invisible to visible. */
val animateReveal: Boolean = false,
@@ -33,7 +31,7 @@ data class KeyguardQuickAffordanceViewModel(
val isActivated: Boolean = false,
) {
data class OnClickedParameters(
- val configKey: KClass<out KeyguardQuickAffordanceConfig>,
+ val configKey: String,
val expandable: Expandable?,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 6b46d8f30cad..cbb670ebf02d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -43,7 +43,8 @@ import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.NotifPanelEvents
+import com.android.systemui.shade.ShadeStateEvents
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -96,7 +97,7 @@ constructor(
private val dreamOverlayStateController: DreamOverlayStateController,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- panelEventsEvents: NotifPanelEvents,
+ panelEventsEvents: ShadeStateEvents,
private val secureSettings: SecureSettings,
@Main private val handler: Handler,
) {
@@ -534,8 +535,8 @@ constructor(
mediaHosts.forEach { it?.updateViewVisibility() }
}
- panelEventsEvents.registerListener(
- object : NotifPanelEvents.Listener {
+ panelEventsEvents.addShadeStateEventsListener(
+ object : ShadeStateEventsListener {
override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
skipQqsOnExpansion = isExpandImmediateEnabled
updateDesiredLocation()
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 38c971ed3f7d..120f7d673881 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
@@ -43,6 +43,21 @@ class MediaTttLogger(
)
}
+ /**
+ * Logs an error in trying to update to [displayState].
+ *
+ * [displayState] is either a [android.app.StatusBarManager.MediaTransferSenderState] or
+ * a [android.app.StatusBarManager.MediaTransferReceiverState].
+ */
+ fun logStateChangeError(displayState: Int) {
+ buffer.log(
+ tag,
+ LogLevel.ERROR,
+ { int1 = displayState },
+ { "Cannot display state=$int1; aborting" }
+ )
+ }
+
/** 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 0a6043793ef6..769494a58842 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
@@ -27,10 +27,11 @@ import com.android.systemui.common.shared.model.Icon
/** Utility methods for media tap-to-transfer. */
class MediaTttUtils {
companion object {
- // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
- // UpdateMediaTapToTransferReceiverDisplayTest
- const val WINDOW_TITLE = "Media Transfer Chip View"
- const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
+ const val WINDOW_TITLE_SENDER = "Media Transfer Chip View (Sender)"
+ const val WINDOW_TITLE_RECEIVER = "Media Transfer Chip View (Receiver)"
+
+ const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER"
+ const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
/**
* Returns the information needed to display the icon in [Icon] form.
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 089625ca8d9c..7dd9fb4b9cd5 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
@@ -25,7 +25,6 @@ import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
-import android.util.Log
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
@@ -41,7 +40,6 @@ import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.media.taptotransfer.common.MediaTttUtils
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
import com.android.systemui.temporarydisplay.TemporaryViewInfo
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -79,8 +77,6 @@ class MediaTttChipControllerReceiver @Inject constructor(
configurationController,
powerManager,
R.layout.media_ttt_chip_receiver,
- MediaTttUtils.WINDOW_TITLE,
- MediaTttUtils.WAKE_REASON,
) {
@SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -116,7 +112,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
- Log.e(RECEIVER_TAG, "Unhandled MediaTransferReceiverState $displayState")
+ logger.logStateChangeError(displayState)
return
}
uiEventLogger.logReceiverStateChange(chipState)
@@ -232,9 +228,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
data class ChipReceiverInfo(
val routeInfo: MediaRoute2Info,
val appIconDrawableOverride: Drawable?,
- val appNameOverride: CharSequence?
-) : TemporaryViewInfo {
- override fun getTimeoutMs() = DEFAULT_TIMEOUT_MILLIS
-}
-
-private const val RECEIVER_TAG = "MediaTapToTransferRcvr"
+ val appNameOverride: CharSequence?,
+ override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
+ override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
+) : TemporaryViewInfo()
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 6e596ee1f473..af7317c208ab 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
@@ -43,7 +43,7 @@ enum class ChipStateSender(
@StringRes val stringResId: Int?,
val transferStatus: TransferStatus,
val endItem: SenderEndItem?,
- val timeout: Long = DEFAULT_TIMEOUT_MILLIS
+ val timeout: Int = DEFAULT_TIMEOUT_MILLIS,
) {
/**
* A state representing that the two devices are close but not close enough to *start* a cast to
@@ -223,6 +223,6 @@ sealed class SenderEndItem {
// Give the Transfer*Triggered states a longer timeout since those states represent an active
// process and we should keep the user informed about it as long as possible (but don't allow it to
// continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000
private const val TAG = "ChipStateSender"
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 edf759ddfd22..d1ea2d0c83bd 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
@@ -19,7 +19,6 @@ package com.android.systemui.media.taptotransfer.sender
import android.app.StatusBarManager
import android.content.Context
import android.media.MediaRoute2Info
-import android.util.Log
import android.view.View
import com.android.internal.logging.UiEventLogger
import com.android.internal.statusbar.IUndoMediaTransferCallback
@@ -34,7 +33,6 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import com.android.systemui.temporarydisplay.chipbar.ChipbarEndItem
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
-import com.android.systemui.temporarydisplay.chipbar.SENDER_TAG
import javax.inject.Inject
/**
@@ -86,7 +84,7 @@ constructor(
logger.logStateChange(stateName, routeInfo.id, routeInfo.clientPackageName)
if (chipState == null) {
- Log.e(SENDER_TAG, "Unhandled MediaTransferSenderState $displayState")
+ logger.logStateChangeError(displayState)
return
}
uiEventLogger.logSenderStateChange(chipState)
@@ -161,6 +159,9 @@ constructor(
}
},
vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+ windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
+ wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
+ timeoutMs = chipStateSender.timeout,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index c089511a7ce9..50cf63d52909 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,11 +25,16 @@ import static android.app.StatusBarManager.WindowVisibleState;
import static android.app.StatusBarManager.windowStateToString;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -77,6 +82,7 @@ import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InsetsFrameProvider;
@@ -240,6 +246,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private boolean mTransientShown;
private boolean mTransientShownFromGestureOnSystemBar;
+ /**
+ * This is to indicate whether the navigation bar button is forced visible. This is true
+ * when the setup wizard is on display. When that happens, the window frame should be provided
+ * as insets size directly.
+ */
+ private boolean mIsButtonForceVisible;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
private final LightBarController mMainLightBarController;
@@ -623,6 +635,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mView.setTouchHandler(mTouchHandler);
setNavBarMode(mNavBarMode);
mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
+ mEdgeBackGestureHandler.setButtonForceVisibleChangeCallback((forceVisible) -> {
+ mIsButtonForceVisible = forceVisible;
+ repositionNavigationBar(mCurrentRotation);
+ });
mNavigationBarTransitions.addListener(this::onBarTransition);
mView.updateRotationButton();
@@ -810,7 +826,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mLayoutDirection = ld;
refreshLayout(ld);
}
-
repositionNavigationBar(rotation);
if (canShowSecondaryHandle()) {
if (rotation != mCurrentRotation) {
@@ -1599,23 +1614,15 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
width,
height,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.gravity = gravity;
- if (insetsHeight != -1) {
- lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight))
- };
- } else {
- lp.providedInsets = new InsetsFrameProvider[] {
- new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
- };
- }
+ lp.providedInsets = getInsetsFrameProvider(insetsHeight, userContext);
+
lp.token = new Binder();
lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
@@ -1628,6 +1635,68 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
return lp;
}
+ private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
+ final InsetsFrameProvider navBarProvider;
+ if (insetsHeight != -1 && !mIsButtonForceVisible) {
+ navBarProvider = new InsetsFrameProvider(
+ ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight));
+ // Use window frame for IME.
+ navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[] {
+ new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+ };
+ } else {
+ navBarProvider = new InsetsFrameProvider(ITYPE_NAVIGATION_BAR);
+ navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[]{
+ new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
+ };
+ }
+ final boolean navBarTapThrough = userContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_navBarTapThrough);
+ final InsetsFrameProvider bottomTappableProvider;
+ if (navBarTapThrough) {
+ bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT,
+ Insets.of(0, 0, 0, 0));
+ } else {
+ bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT);
+ }
+
+ if (!mEdgeBackGestureHandler.isHandlingGestures()) {
+ // 2/3 button navigation is on. Do not provide any gesture insets here. But need to keep
+ // the provider to support runtime update.
+ return new InsetsFrameProvider[] {
+ navBarProvider,
+ new InsetsFrameProvider(
+ ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.NONE),
+ new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.NONE, null),
+ new InsetsFrameProvider(ITYPE_RIGHT_GESTURES,
+ InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.NONE, null),
+ bottomTappableProvider
+ };
+ } else {
+ // Gesture navigation
+ final int gestureHeight = userContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
+ final DisplayCutout cutout = userContext.getDisplay().getCutout();
+ final int safeInsetsLeft = cutout != null ? cutout.getSafeInsetLeft() : 0;
+ final int safeInsetsRight = cutout != null ? cutout.getSafeInsetRight() : 0;
+ return new InsetsFrameProvider[] {
+ navBarProvider,
+ new InsetsFrameProvider(
+ ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.of(0, 0, 0, gestureHeight)),
+ new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.of(safeInsetsLeft
+ + mEdgeBackGestureHandler.getEdgeWidthLeft(), 0, 0, 0), null),
+ new InsetsFrameProvider(ITYPE_RIGHT_GESTURES,
+ InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.of(0, 0, safeInsetsRight
+ + mEdgeBackGestureHandler.getEdgeWidthRight(), 0), null),
+ bottomTappableProvider
+ };
+ }
+ }
+
private boolean canShowSecondaryHandle() {
return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
}
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 709467ffd3b5..10ff48babe16 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -175,6 +175,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
private Runnable mStateChangeCallback;
+ private Consumer<Boolean> mButtonForceVisibleCallback;
private final PluginManager mPluginManager;
private final ProtoTracer mProtoTracer;
@@ -240,6 +241,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
private boolean mIsNewBackAffordanceEnabled;
+ private boolean mIsButtonForceVisible;
private InputMonitor mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@@ -402,12 +404,29 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mStateChangeCallback = callback;
}
+ public void setButtonForceVisibleChangeCallback(Consumer<Boolean> callback) {
+ mButtonForceVisibleCallback = callback;
+ }
+
+ public int getEdgeWidthLeft() {
+ return mEdgeWidthLeft;
+ }
+
+ public int getEdgeWidthRight() {
+ return mEdgeWidthRight;
+ }
+
public void updateCurrentUserResources() {
Resources res = mNavigationModeController.getCurrentUserContext().getResources();
mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
- mIsBackGestureAllowed =
- !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ final boolean previousForceVisible = mIsButtonForceVisible;
+ mIsButtonForceVisible =
+ mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ if (previousForceVisible != mIsButtonForceVisible && mButtonForceVisibleCallback != null) {
+ mButtonForceVisibleCallback.accept(mIsButtonForceVisible);
+ }
+ mIsBackGestureAllowed = !mIsButtonForceVisible;
final DisplayMetrics dm = res.getDisplayMetrics();
final float defaultGestureHeight = res.getDimension(
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
new file mode 100644
index 000000000000..d247f249e2fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -0,0 +1,72 @@
+/*
+ * 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
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.os.UserManager
+import android.view.KeyEvent
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.Optional
+import javax.inject.Inject
+
+/**
+ * Entry point for creating and managing note.
+ *
+ * The controller decides how a note is launched based in the device state: locked or unlocked.
+ *
+ * Currently, we only support a single task per time.
+ */
+@SysUISingleton
+internal class NoteTaskController
+@Inject
+constructor(
+ private val context: Context,
+ private val intentResolver: NoteTaskIntentResolver,
+ private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val optionalKeyguardManager: Optional<KeyguardManager>,
+ private val optionalUserManager: Optional<UserManager>,
+ @NoteTaskEnabledKey private val isEnabled: Boolean,
+) {
+
+ fun handleSystemKey(keyCode: Int) {
+ if (!isEnabled) return
+
+ if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+ showNoteTask()
+ }
+ }
+
+ private fun showNoteTask() {
+ val floatingTasks = optionalFloatingTasks.getOrNull() ?: return
+ val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
+ val userManager = optionalUserManager.getOrNull() ?: return
+ val intent = intentResolver.resolveIntent() ?: return
+
+ // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
+ if (!userManager.isUserUnlocked) return
+
+ if (keyguardManager.isKeyguardLocked) {
+ context.startActivity(intent)
+ } else {
+ // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
+ floatingTasks.showOrSetStashed(intent)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
new file mode 100644
index 000000000000..e0bf1da2f652
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEnabledKey.kt
@@ -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 com.android.systemui.notetask
+
+import javax.inject.Qualifier
+
+/** Key associated with a [Boolean] flag that enables or disables the note task feature. */
+@Qualifier internal annotation class NoteTaskEnabledKey
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
new file mode 100644
index 000000000000..d84717da3a21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+import com.android.systemui.statusbar.CommandQueue
+import com.android.wm.shell.floating.FloatingTasks
+import dagger.Lazy
+import java.util.Optional
+import javax.inject.Inject
+
+/** Class responsible to "glue" all note task dependencies. */
+internal class NoteTaskInitializer
+@Inject
+constructor(
+ private val optionalFloatingTasks: Optional<FloatingTasks>,
+ private val lazyNoteTaskController: Lazy<NoteTaskController>,
+ private val commandQueue: CommandQueue,
+ @NoteTaskEnabledKey private val isEnabled: Boolean,
+) {
+
+ private val callbacks =
+ object : CommandQueue.Callbacks {
+ override fun handleSystemKey(keyCode: Int) {
+ lazyNoteTaskController.get().handleSystemKey(keyCode)
+ }
+ }
+
+ fun initialize() {
+ if (isEnabled && optionalFloatingTasks.isPresent) {
+ commandQueue.addCallback(callbacks)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
new file mode 100644
index 000000000000..98d69910aac3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskIntentResolver.kt
@@ -0,0 +1,81 @@
+/*
+ * 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
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import javax.inject.Inject
+
+/**
+ * Class responsible to query all apps and find one that can handle the [NOTES_ACTION]. If found, an
+ * [Intent] ready for be launched will be returned. Otherwise, returns null.
+ *
+ * TODO(b/248274123): should be revisited once the notes role is implemented.
+ */
+internal class NoteTaskIntentResolver
+@Inject
+constructor(
+ private val packageManager: PackageManager,
+) {
+
+ fun resolveIntent(): Intent? {
+ val intent = Intent(NOTES_ACTION)
+ val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
+ val infoList = packageManager.queryIntentActivities(intent, flags)
+
+ for (info in infoList) {
+ val packageName = info.serviceInfo.applicationInfo.packageName ?: continue
+ val activityName = resolveActivityNameForNotesAction(packageName) ?: continue
+
+ return Intent(NOTES_ACTION)
+ .setComponent(ComponentName(packageName, activityName))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ return null
+ }
+
+ private fun resolveActivityNameForNotesAction(packageName: String): String? {
+ val intent = Intent(NOTES_ACTION).setPackage(packageName)
+ val flags = ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
+ val resolveInfo = packageManager.resolveActivity(intent, flags)
+
+ val activityInfo = resolveInfo?.activityInfo ?: return null
+ if (activityInfo.name.isNullOrBlank()) return null
+ if (!activityInfo.exported) return null
+ if (!activityInfo.enabled) return null
+ if (!activityInfo.showWhenLocked) return null
+ if (!activityInfo.turnScreenOn) return null
+
+ return activityInfo.name
+ }
+
+ companion object {
+ // TODO(b/254606432): Use Intent.ACTION_NOTES and Intent.ACTION_NOTES_LOCKED instead.
+ const val NOTES_ACTION = "android.intent.action.NOTES"
+ }
+}
+
+private val ActivityInfo.showWhenLocked: Boolean
+ get() = flags and ActivityInfo.FLAG_SHOW_WHEN_LOCKED != 0
+
+private val ActivityInfo.turnScreenOn: Boolean
+ get() = flags and ActivityInfo.FLAG_TURN_SCREEN_ON != 0
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
new file mode 100644
index 000000000000..035396a6fc76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+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 dagger.Module
+import dagger.Provides
+import java.util.*
+
+/** Compose all dependencies required by Note Task feature. */
+@Module
+internal class NoteTaskModule {
+
+ @[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())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index b6b657ec82f6..57a00c9a1620 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -204,6 +204,15 @@ public class UserDetailView extends PseudoGridView {
Trace.endSection();
}
+ @Override
+ public void onUserListItemClicked(@NonNull UserRecord record,
+ @Nullable UserSwitchDialogController.DialogShower dialogShower) {
+ if (dialogShower != null) {
+ mDialogShower.dismiss();
+ }
+ super.onUserListItemClicked(record, dialogShower);
+ }
+
public void linkToViewGroup(ViewGroup viewGroup) {
PseudoGridView.ViewGroupAdapterBridge.link(viewGroup, this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 24c4723d8b50..a895d72a7492 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -39,6 +39,7 @@ import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
@@ -62,6 +63,7 @@ import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -72,6 +74,8 @@ import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
/**
* Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
*/
@@ -86,6 +90,7 @@ public class InternetDialog extends SystemUIDialog implements
private final Handler mHandler;
private final Executor mBackgroundExecutor;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
@VisibleForTesting
protected InternetAdapter mAdapter;
@@ -109,6 +114,7 @@ public class InternetDialog extends SystemUIDialog implements
private LinearLayout mInternetDialogLayout;
private LinearLayout mConnectedWifListLayout;
private LinearLayout mMobileNetworkLayout;
+ private LinearLayout mSecondaryMobileNetworkLayout;
private LinearLayout mTurnWifiOnLayout;
private LinearLayout mEthernetLayout;
private TextView mWifiToggleTitleText;
@@ -123,6 +129,8 @@ public class InternetDialog extends SystemUIDialog implements
private ImageView mSignalIcon;
private TextView mMobileTitleText;
private TextView mMobileSummaryText;
+ private TextView mSecondaryMobileTitleText;
+ private TextView mSecondaryMobileSummaryText;
private TextView mAirplaneModeSummaryText;
private Switch mMobileDataToggle;
private View mMobileToggleDivider;
@@ -158,9 +166,11 @@ public class InternetDialog extends SystemUIDialog implements
mInternetDialogSubTitle.setText(getSubtitleText());
};
+ @Inject
public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
InternetDialogController internetDialogController, boolean canConfigMobileData,
boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
+ DialogLaunchAnimator dialogLaunchAnimator,
@Main Handler handler, @Background Executor executor,
KeyguardStateController keyguardStateController) {
super(context);
@@ -183,6 +193,7 @@ public class InternetDialog extends SystemUIDialog implements
mKeyguard = keyguardStateController;
mUiEventLogger = uiEventLogger;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
mAdapter = new InternetAdapter(mInternetDialogController);
if (!aboveStatusBar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
@@ -287,6 +298,9 @@ public class InternetDialog extends SystemUIDialog implements
mMobileNetworkLayout.setOnClickListener(null);
mMobileDataToggle.setOnCheckedChangeListener(null);
mConnectedWifListLayout.setOnClickListener(null);
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setOnClickListener(null);
+ }
mSeeAllLayout.setOnClickListener(null);
mWiFiToggle.setOnCheckedChangeListener(null);
mDoneButton.setOnClickListener(null);
@@ -341,6 +355,10 @@ public class InternetDialog extends SystemUIDialog implements
private void setOnClickListener() {
mMobileNetworkLayout.setOnClickListener(v -> {
+ int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ if (autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ showTurnOffAutoDataSwitchDialog(autoSwitchNonDdsSubId);
+ }
mInternetDialogController.connectCarrierNetwork();
});
mMobileDataToggle.setOnCheckedChangeListener(
@@ -385,11 +403,14 @@ public class InternetDialog extends SystemUIDialog implements
if (!mInternetDialogController.hasActiveSubId()
&& (!isWifiEnabled || !isCarrierNetworkActive)) {
mMobileNetworkLayout.setVisibility(View.GONE);
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
+ }
} else {
mMobileNetworkLayout.setVisibility(View.VISIBLE);
mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
- mMobileTitleText.setText(getMobileNetworkTitle());
- String summary = getMobileNetworkSummary();
+ mMobileTitleText.setText(getMobileNetworkTitle(mDefaultDataSubId));
+ String summary = getMobileNetworkSummary(mDefaultDataSubId);
if (!TextUtils.isEmpty(summary)) {
mMobileSummaryText.setText(
Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY));
@@ -399,28 +420,11 @@ public class InternetDialog extends SystemUIDialog implements
mMobileSummaryText.setVisibility(View.GONE);
}
mBackgroundExecutor.execute(() -> {
- Drawable drawable = getSignalStrengthDrawable();
+ Drawable drawable = getSignalStrengthDrawable(mDefaultDataSubId);
mHandler.post(() -> {
mSignalIcon.setImageDrawable(drawable);
});
});
- mMobileTitleText.setTextAppearance(isNetworkConnected
- ? R.style.TextAppearance_InternetDialog_Active
- : R.style.TextAppearance_InternetDialog);
- int secondaryRes = isNetworkConnected
- ? R.style.TextAppearance_InternetDialog_Secondary_Active
- : R.style.TextAppearance_InternetDialog_Secondary;
- mMobileSummaryText.setTextAppearance(secondaryRes);
- // Set airplane mode to the summary for carrier network
- if (mInternetDialogController.isAirplaneModeEnabled()) {
- mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
- mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode));
- mAirplaneModeSummaryText.setTextAppearance(secondaryRes);
- } else {
- mAirplaneModeSummaryText.setVisibility(View.GONE);
- }
- mMobileNetworkLayout.setBackground(
- isNetworkConnected ? mBackgroundOn : mBackgroundOff);
TypedArray array = mContext.obtainStyledAttributes(
R.style.InternetDialog_Divider_Active, new int[]{android.R.attr.background});
@@ -433,6 +437,86 @@ public class InternetDialog extends SystemUIDialog implements
mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
mMobileToggleDivider.setVisibility(
mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+
+ // Display the info for the non-DDS if it's actively being used
+ int autoSwitchNonDdsSubId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ int nonDdsVisibility = autoSwitchNonDdsSubId
+ != SubscriptionManager.INVALID_SUBSCRIPTION_ID ? View.VISIBLE : View.GONE;
+
+ int secondaryRes = isNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Secondary_Active
+ : R.style.TextAppearance_InternetDialog_Secondary;
+ if (nonDdsVisibility == View.VISIBLE) {
+ // non DDS is the currently active sub, set primary visual for it
+ ViewStub stub = mDialogView.findViewById(R.id.secondary_mobile_network_stub);
+ if (stub != null) {
+ stub.inflate();
+ }
+ mSecondaryMobileNetworkLayout = findViewById(R.id.secondary_mobile_network_layout);
+ mSecondaryMobileNetworkLayout.setOnClickListener(
+ this::onClickConnectedSecondarySub);
+ mSecondaryMobileNetworkLayout.setBackground(mBackgroundOn);
+
+ mSecondaryMobileTitleText = mDialogView.requireViewById(
+ R.id.secondary_mobile_title);
+ mSecondaryMobileTitleText.setText(getMobileNetworkTitle(autoSwitchNonDdsSubId));
+ mSecondaryMobileTitleText.setTextAppearance(
+ R.style.TextAppearance_InternetDialog_Active);
+
+ mSecondaryMobileSummaryText =
+ mDialogView.requireViewById(R.id.secondary_mobile_summary);
+ summary = getMobileNetworkSummary(autoSwitchNonDdsSubId);
+ if (!TextUtils.isEmpty(summary)) {
+ mSecondaryMobileSummaryText.setText(
+ Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY));
+ mSecondaryMobileSummaryText.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
+ mSecondaryMobileSummaryText.setTextAppearance(
+ R.style.TextAppearance_InternetDialog_Active);
+ }
+
+ ImageView mSecondarySignalIcon =
+ mDialogView.requireViewById(R.id.secondary_signal_icon);
+ mBackgroundExecutor.execute(() -> {
+ Drawable drawable = getSignalStrengthDrawable(autoSwitchNonDdsSubId);
+ mHandler.post(() -> {
+ mSecondarySignalIcon.setImageDrawable(drawable);
+ });
+ });
+
+ ImageView mSecondaryMobileSettingsIcon =
+ mDialogView.requireViewById(R.id.secondary_settings_icon);
+ mSecondaryMobileSettingsIcon.setColorFilter(
+ mContext.getColor(R.color.connected_network_primary_color));
+
+ // set secondary visual for default data sub
+ mMobileNetworkLayout.setBackground(mBackgroundOff);
+ mMobileTitleText.setTextAppearance(R.style.TextAppearance_InternetDialog);
+ mMobileSummaryText.setTextAppearance(
+ R.style.TextAppearance_InternetDialog_Secondary);
+ mSignalIcon.setColorFilter(
+ mContext.getColor(R.color.connected_network_secondary_color));
+ } else {
+ mMobileNetworkLayout.setBackground(
+ isNetworkConnected ? mBackgroundOn : mBackgroundOff);
+ mMobileTitleText.setTextAppearance(isNetworkConnected
+ ?
+ R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ mMobileSummaryText.setTextAppearance(secondaryRes);
+ }
+
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(nonDdsVisibility);
+ }
+
+ // Set airplane mode to the summary for carrier network
+ if (mInternetDialogController.isAirplaneModeEnabled()) {
+ mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
+ mAirplaneModeSummaryText.setText(mContext.getText(R.string.airplane_mode));
+ mAirplaneModeSummaryText.setTextAppearance(secondaryRes);
+ } else {
+ mAirplaneModeSummaryText.setVisibility(View.GONE);
+ }
}
}
@@ -471,6 +555,10 @@ public class InternetDialog extends SystemUIDialog implements
mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
mWifiSettingsIcon.setColorFilter(
mContext.getColor(R.color.connected_network_primary_color));
+
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
+ }
}
@MainThread
@@ -541,6 +629,11 @@ public class InternetDialog extends SystemUIDialog implements
mInternetDialogController.launchWifiDetailsSetting(mConnectedWifiEntry.getKey(), view);
}
+ /** For DSDS auto data switch **/
+ void onClickConnectedSecondarySub(View view) {
+ mInternetDialogController.launchMobileNetworkSettings(view);
+ }
+
void onClickSeeMoreButton(View view) {
mInternetDialogController.launchNetworkSetting(view);
}
@@ -555,16 +648,16 @@ public class InternetDialog extends SystemUIDialog implements
mIsProgressBarVisible && !mIsSearchingHidden);
}
- private Drawable getSignalStrengthDrawable() {
- return mInternetDialogController.getSignalStrengthDrawable();
+ private Drawable getSignalStrengthDrawable(int subId) {
+ return mInternetDialogController.getSignalStrengthDrawable(subId);
}
- CharSequence getMobileNetworkTitle() {
- return mInternetDialogController.getMobileNetworkTitle();
+ CharSequence getMobileNetworkTitle(int subId) {
+ return mInternetDialogController.getMobileNetworkTitle(subId);
}
- String getMobileNetworkSummary() {
- return mInternetDialogController.getMobileNetworkSummary();
+ String getMobileNetworkSummary(int subId) {
+ return mInternetDialogController.getMobileNetworkSummary(subId);
}
protected void showProgressBar() {
@@ -602,8 +695,8 @@ public class InternetDialog extends SystemUIDialog implements
}
private void showTurnOffMobileDialog() {
- CharSequence carrierName = getMobileNetworkTitle();
- boolean isInService = mInternetDialogController.isVoiceStateInService();
+ CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId);
+ boolean isInService = mInternetDialogController.isVoiceStateInService(mDefaultDataSubId);
if (TextUtils.isEmpty(carrierName) || !isInService) {
carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
}
@@ -627,7 +720,33 @@ public class InternetDialog extends SystemUIDialog implements
SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
SystemUIDialog.registerDismissListener(mAlertDialog);
SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing());
- mAlertDialog.show();
+ mDialogLaunchAnimator.showFromDialog(mAlertDialog, this, null, false);
+ }
+
+ private void showTurnOffAutoDataSwitchDialog(int subId) {
+ CharSequence carrierName = getMobileNetworkTitle(mDefaultDataSubId);
+ if (TextUtils.isEmpty(carrierName)) {
+ carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
+ }
+ mAlertDialog = new Builder(mContext)
+ .setTitle(mContext.getString(R.string.auto_data_switch_disable_title, carrierName))
+ .setMessage(R.string.auto_data_switch_disable_message)
+ .setNegativeButton(R.string.auto_data_switch_dialog_negative_button,
+ (d, w) -> {})
+ .setPositiveButton(R.string.auto_data_switch_dialog_positive_button,
+ (d, w) -> {
+ mInternetDialogController
+ .setAutoDataSwitchMobileDataPolicy(subId, false);
+ if (mSecondaryMobileNetworkLayout != null) {
+ mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
+ }
+ })
+ .create();
+ mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
+ SystemUIDialog.registerDismissListener(mAlertDialog);
+ SystemUIDialog.setWindowOnTop(mAlertDialog, mKeyguard.isShowing());
+ mDialogLaunchAnimator.showFromDialog(mAlertDialog, this, null, false);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 0e00c46bc77f..aa6e67841e78 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -37,6 +37,7 @@ import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.wifi.WifiManager;
+import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -78,6 +79,8 @@ import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
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.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -90,6 +93,7 @@ import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -113,6 +117,17 @@ public class InternetDialogController implements AccessPointController.AccessPoi
"android.settings.NETWORK_PROVIDER_SETTINGS";
private static final String ACTION_WIFI_SCANNING_SETTINGS =
"android.settings.WIFI_SCANNING_SETTINGS";
+ /**
+ * Fragment "key" argument passed thru {@link #SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS}
+ */
+ private static final String SETTINGS_EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+ /**
+ * When starting this activity, this extra can also be specified to supply a Bundle of arguments
+ * to pass to that fragment when it is instantiated during the initial creation of the activity.
+ */
+ private static final String SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS =
+ ":settings:show_fragment_args";
+ private static final String AUTO_DATA_SWITCH_SETTING_R_ID = "auto_data_switch";
public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
public static final int NO_CELL_DATA_TYPE_ICON = 0;
private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
@@ -130,9 +145,12 @@ public class InternetDialogController implements AccessPointController.AccessPoi
static final int MAX_WIFI_ENTRY_COUNT = 3;
+ private final FeatureFlags mFeatureFlags;
+
private WifiManager mWifiManager;
private Context mContext;
private SubscriptionManager mSubscriptionManager;
+ private Map<Integer, TelephonyManager> mSubIdTelephonyManagerMap = new HashMap<>();
private TelephonyManager mTelephonyManager;
private ConnectivityManager mConnectivityManager;
private CarrierConfigTracker mCarrierConfigTracker;
@@ -155,6 +173,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
private WindowManager mWindowManager;
private ToastFactory mToastFactory;
private SignalDrawable mSignalDrawable;
+ private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS
private LocationController mLocationController;
private DialogLaunchAnimator mDialogLaunchAnimator;
private boolean mHasWifiEntries;
@@ -213,7 +232,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi
CarrierConfigTracker carrierConfigTracker,
LocationController locationController,
DialogLaunchAnimator dialogLaunchAnimator,
- WifiStateWorker wifiStateWorker
+ WifiStateWorker wifiStateWorker,
+ FeatureFlags featureFlags
) {
if (DEBUG) {
Log.d(TAG, "Init InternetDialogController");
@@ -242,10 +262,12 @@ public class InternetDialogController implements AccessPointController.AccessPoi
mWindowManager = windowManager;
mToastFactory = toastFactory;
mSignalDrawable = new SignalDrawable(mContext);
+ mSecondarySignalDrawable = new SignalDrawable(mContext);
mLocationController = locationController;
mDialogLaunchAnimator = dialogLaunchAnimator;
mConnectedWifiInternetMonitor = new ConnectedWifiInternetMonitor();
mWifiStateWorker = wifiStateWorker;
+ mFeatureFlags = featureFlags;
}
void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
@@ -267,6 +289,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
}
mConfig = MobileMappings.Config.readConfig(mContext);
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
mInternetTelephonyCallback = new InternetTelephonyCallback();
mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
// Listen the connectivity changes
@@ -280,7 +303,9 @@ public class InternetDialogController implements AccessPointController.AccessPoi
Log.d(TAG, "onStop");
}
mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
- mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ for (TelephonyManager tm : mSubIdTelephonyManagerMap.values()) {
+ tm.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ }
mSubscriptionManager.removeOnSubscriptionsChangedListener(
mOnSubscriptionsChangedListener);
mAccessPointController.removeAccessPointCallback(this);
@@ -371,7 +396,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi
if (DEBUG) {
Log.d(TAG, "No Wi-Fi item.");
}
- if (!hasActiveSubId() || (!isVoiceStateInService() && !isDataStateInService())) {
+ boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager
+ .INVALID_SUBSCRIPTION_ID;
+ if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId)
+ && !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) {
if (DEBUG) {
Log.d(TAG, "No carrier or service is out of service.");
}
@@ -412,7 +440,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
return drawable;
}
- Drawable getSignalStrengthDrawable() {
+ Drawable getSignalStrengthDrawable(int subId) {
Drawable drawable = mContext.getDrawable(
R.drawable.ic_signal_strength_zero_bar_no_internet);
try {
@@ -424,9 +452,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi
}
boolean isCarrierNetworkActive = isCarrierNetworkActive();
- if (isDataStateInService() || isVoiceStateInService() || isCarrierNetworkActive) {
+ if (isDataStateInService(subId) || isVoiceStateInService(subId)
+ || isCarrierNetworkActive) {
AtomicReference<Drawable> shared = new AtomicReference<>();
- shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive));
+ shared.set(getSignalStrengthDrawableWithLevel(isCarrierNetworkActive, subId));
drawable = shared.get();
}
@@ -447,24 +476,30 @@ public class InternetDialogController implements AccessPointController.AccessPoi
*
* @return The Drawable which is a signal bar icon with level.
*/
- Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive) {
- final SignalStrength strength = mTelephonyManager.getSignalStrength();
+ Drawable getSignalStrengthDrawableWithLevel(boolean isCarrierNetworkActive, int subId) {
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ final SignalStrength strength = tm.getSignalStrength();
int level = (strength == null) ? 0 : strength.getLevel();
int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
if (isCarrierNetworkActive) {
level = getCarrierNetworkLevel();
numLevels = WifiEntry.WIFI_LEVEL_MAX + 1;
- } else if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
+ } else if (mSubscriptionManager != null && shouldInflateSignalStrength(subId)) {
level += 1;
numLevels += 1;
}
- return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
+ return getSignalStrengthIcon(subId, mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
!isMobileDataEnabled());
}
- Drawable getSignalStrengthIcon(Context context, int level, int numLevels,
+ Drawable getSignalStrengthIcon(int subId, Context context, int level, int numLevels,
int iconType, boolean cutOut) {
- mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ boolean isForDds = subId == mDefaultDataSubId;
+ if (isForDds) {
+ mSignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ } else {
+ mSecondarySignalDrawable.setLevel(SignalDrawable.getState(level, numLevels, cutOut));
+ }
// Make the network type drawable
final Drawable networkDrawable =
@@ -473,7 +508,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi
: context.getResources().getDrawable(iconType, context.getTheme());
// Overlay the two drawables
- final Drawable[] layers = {networkDrawable, mSignalDrawable};
+ final Drawable[] layers = {networkDrawable, isForDds
+ ? mSignalDrawable : mSecondarySignalDrawable};
final int iconSize =
context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
@@ -571,14 +607,39 @@ public class InternetDialogController implements AccessPointController.AccessPoi
info -> info.uniqueName));
}
- CharSequence getMobileNetworkTitle() {
- return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext);
+ /**
+ * @return the subId of the visible non-DDS if it's actively being used for data, otherwise
+ * return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+ */
+ int getActiveAutoSwitchNonDdsSubId() {
+ if (!mFeatureFlags.isEnabled(Flags.QS_SECONDARY_DATA_SUB_INFO)) {
+ // sets the non-DDS to be not found to hide its visual
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ }
+ SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(
+ SubscriptionManager.getActiveDataSubscriptionId());
+ if (subInfo != null && subInfo.getSubscriptionId() != mDefaultDataSubId
+ && !subInfo.isOpportunistic()) {
+ int subId = subInfo.getSubscriptionId();
+ if (mSubIdTelephonyManagerMap.get(subId) == null) {
+ TelephonyManager secondaryTm = mTelephonyManager.createForSubscriptionId(subId);
+ secondaryTm.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+ mSubIdTelephonyManagerMap.put(subId, secondaryTm);
+ }
+ return subId;
+ }
+ return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+ }
+
+ CharSequence getMobileNetworkTitle(int subId) {
+ return getUniqueSubscriptionDisplayName(subId, mContext);
}
- String getMobileNetworkSummary() {
+ String getMobileNetworkSummary(int subId) {
String description = getNetworkTypeDescription(mContext, mConfig,
- mTelephonyDisplayInfo, mDefaultDataSubId);
- return getMobileSummary(mContext, description);
+ mTelephonyDisplayInfo, subId);
+ return getMobileSummary(mContext, description, subId);
}
/**
@@ -606,22 +667,28 @@ public class InternetDialogController implements AccessPointController.AccessPoi
? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : "";
}
- private String getMobileSummary(Context context, String networkTypeDescription) {
+ private String getMobileSummary(Context context, String networkTypeDescription, int subId) {
if (!isMobileDataEnabled()) {
return context.getString(R.string.mobile_data_off_summary);
}
String summary = networkTypeDescription;
+ boolean isForDds = subId == mDefaultDataSubId;
+ int activeSubId = getActiveAutoSwitchNonDdsSubId();
+ boolean isOnNonDds = activeSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID;
// Set network description for the carrier network when connecting to the carrier network
// under the airplane mode ON.
if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
summary = context.getString(R.string.preference_summary_default_combination,
- context.getString(R.string.mobile_data_connection_active),
+ context.getString(
+ isForDds // if nonDds is active, explains Dds status as poor connection
+ ? (isOnNonDds ? R.string.mobile_data_poor_connection
+ : R.string.mobile_data_connection_active)
+ : R.string.mobile_data_temp_connection_active),
networkTypeDescription);
- } else if (!isDataStateInService()) {
+ } else if (!isDataStateInService(subId)) {
summary = context.getString(R.string.mobile_data_no_connection);
}
-
return summary;
}
@@ -647,6 +714,26 @@ public class InternetDialogController implements AccessPointController.AccessPoi
}
}
+ void launchMobileNetworkSettings(View view) {
+ final int subId = getActiveAutoSwitchNonDdsSubId();
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ Log.w(TAG, "launchMobileNetworkSettings fail, invalid subId:" + subId);
+ return;
+ }
+ startActivity(getSubSettingIntent(subId), view);
+ }
+
+ Intent getSubSettingIntent(int subId) {
+ final Intent intent = new Intent(Settings.ACTION_NETWORK_OPERATOR_SETTINGS);
+
+ final Bundle fragmentArgs = new Bundle();
+ // Special contract for Settings to highlight permission row
+ fragmentArgs.putString(SETTINGS_EXTRA_FRAGMENT_ARG_KEY, AUTO_DATA_SWITCH_SETTING_R_ID);
+ fragmentArgs.putInt(Settings.EXTRA_SUB_ID, subId);
+ intent.putExtra(SETTINGS_EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
+ return intent;
+ }
+
void launchWifiScanningSetting(View view) {
final Intent intent = new Intent(ACTION_WIFI_SCANNING_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -824,8 +911,20 @@ public class InternetDialogController implements AccessPointController.AccessPoi
mWorkerHandler.post(() -> setMergedCarrierWifiEnabledIfNeed(subId, enabled));
}
- boolean isDataStateInService() {
- final ServiceState serviceState = mTelephonyManager.getServiceState();
+ void setAutoDataSwitchMobileDataPolicy(int subId, boolean enable) {
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ if (tm == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not set mobile data.");
+ }
+ return;
+ }
+ tm.setMobileDataPolicyEnabled(TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, enable);
+ }
+
+ boolean isDataStateInService(int subId) {
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ final ServiceState serviceState = tm.getServiceState();
NetworkRegistrationInfo regInfo =
(serviceState == null) ? null : serviceState.getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS,
@@ -833,7 +932,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
return (regInfo == null) ? false : regInfo.isRegistered();
}
- boolean isVoiceStateInService() {
+ boolean isVoiceStateInService(int subId) {
if (mTelephonyManager == null) {
if (DEBUG) {
Log.d(TAG, "TelephonyManager is null, can not detect voice state.");
@@ -841,7 +940,8 @@ public class InternetDialogController implements AccessPointController.AccessPoi
return false;
}
- final ServiceState serviceState = mTelephonyManager.getServiceState();
+ TelephonyManager tm = mSubIdTelephonyManagerMap.getOrDefault(subId, mTelephonyManager);
+ final ServiceState serviceState = tm.getServiceState();
return serviceState != null
&& serviceState.getState() == serviceState.STATE_IN_SERVICE;
}
@@ -1132,6 +1232,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
mTelephonyManager.registerTelephonyCallback(mHandler::post,
mInternetTelephonyCallback);
mCallback.onSubscriptionsChanged(mDefaultDataSubId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index 8566ca308738..796672dc0ead 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -66,7 +66,8 @@ class InternetDialogFactory @Inject constructor(
} else {
internetDialog = InternetDialog(
context, this, internetDialogController,
- canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
+ canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger,
+ dialogLaunchAnimator, handler,
executor, keyguardStateController
)
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 231e415f17c6..d524a356a323 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -20,6 +20,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static com.android.systemui.flags.Flags.SCREENSHOT_WORK_PROFILE_POLICY;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
@@ -634,6 +635,11 @@ public class ScreenshotController {
return true;
}
});
+
+ if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mScreenshotView.badgeScreenshot(
+ mContext.getPackageManager().getUserBadgeForDensity(owner, 0));
+ }
mScreenshotView.setScreenshot(mScreenBitmap, screenInsets);
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mScreenshotView);
@@ -1038,7 +1044,7 @@ public class ScreenshotController {
private boolean isUserSetupComplete(UserHandle owner) {
return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
- .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 1b9cdd42dbf7..27331ae7a389 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -74,7 +74,6 @@ import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -122,15 +121,9 @@ public class ScreenshotView extends FrameLayout implements
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
- private static final float ROUNDED_CORNER_RADIUS = .25f;
private static final int SWIPE_PADDING_DP = 12; // extra padding around views to allow swipe
- private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
-
private final Resources mResources;
private final Interpolator mFastOutSlowIn;
private final DisplayMetrics mDisplayMetrics;
@@ -145,6 +138,7 @@ public class ScreenshotView extends FrameLayout implements
private ImageView mScrollingScrim;
private DraggableConstraintLayout mScreenshotStatic;
private ImageView mScreenshotPreview;
+ private ImageView mScreenshotBadge;
private View mScreenshotPreviewBorder;
private ImageView mScrollablePreview;
private ImageView mScreenshotFlash;
@@ -355,6 +349,7 @@ public class ScreenshotView extends FrameLayout implements
mScreenshotPreviewBorder = requireNonNull(
findViewById(R.id.screenshot_preview_border));
mScreenshotPreview.setClipToOutline(true);
+ mScreenshotBadge = requireNonNull(findViewById(R.id.screenshot_badge));
mActionsContainerBackground = requireNonNull(findViewById(
R.id.actions_container_background));
@@ -595,8 +590,11 @@ public class ScreenshotView extends FrameLayout implements
ValueAnimator borderFadeIn = ValueAnimator.ofFloat(0, 1);
borderFadeIn.setDuration(100);
- borderFadeIn.addUpdateListener((animation) ->
- mScreenshotPreviewBorder.setAlpha(animation.getAnimatedFraction()));
+ borderFadeIn.addUpdateListener((animation) -> {
+ float borderAlpha = animation.getAnimatedFraction();
+ mScreenshotPreviewBorder.setAlpha(borderAlpha);
+ mScreenshotBadge.setAlpha(borderAlpha);
+ });
if (showFlash) {
dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
@@ -763,6 +761,11 @@ public class ScreenshotView extends FrameLayout implements
return animator;
}
+ void badgeScreenshot(Drawable badge) {
+ mScreenshotBadge.setImageDrawable(badge);
+ mScreenshotBadge.setVisibility(badge != null ? View.VISIBLE : View.GONE);
+ }
+
void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
@@ -1027,6 +1030,9 @@ public class ScreenshotView extends FrameLayout implements
mScreenshotPreview.setVisibility(View.INVISIBLE);
mScreenshotPreview.setAlpha(1f);
mScreenshotPreviewBorder.setAlpha(0);
+ mScreenshotBadge.setAlpha(0f);
+ mScreenshotBadge.setVisibility(View.GONE);
+ mScreenshotBadge.setImageDrawable(null);
mPendingSharedTransition = false;
mActionsContainerBackground.setVisibility(View.GONE);
mActionsContainer.setVisibility(View.GONE);
@@ -1082,6 +1088,7 @@ public class ScreenshotView extends FrameLayout implements
mActionsContainerBackground.setAlpha(alpha);
mActionsContainer.setAlpha(alpha);
mScreenshotPreviewBorder.setAlpha(alpha);
+ mScreenshotBadge.setAlpha(alpha);
});
alphaAnim.setDuration(600);
return alphaAnim;
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
index bbba0071094b..b36f0d7bacfc 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimDrawable.java
@@ -33,13 +33,13 @@ import android.view.animation.DecelerateInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
/**
* Drawable used on SysUI scrims.
*/
public class ScrimDrawable extends Drawable {
private static final String TAG = "ScrimDrawable";
- private static final long COLOR_ANIMATION_DURATION = 2000;
private final Paint mPaint;
private int mAlpha = 255;
@@ -76,7 +76,7 @@ public class ScrimDrawable extends Drawable {
final int mainFrom = mMainColor;
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- anim.setDuration(COLOR_ANIMATION_DURATION);
+ anim.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
anim.addUpdateListener(animation -> {
float ratio = (float) animation.getAnimatedValue();
mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b39175e9f9f8..1c0f05745280 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -135,7 +135,6 @@ import com.android.systemui.biometrics.AuthController;
import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeLog;
@@ -231,7 +230,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.LargeScreenUtils;
-import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.Utils;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -372,7 +370,6 @@ public final class NotificationPanelViewController {
private final TapAgainViewController mTapAgainViewController;
private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
private final RecordingController mRecordingController;
- private final PanelEventsEmitter mPanelEventsEmitter;
private final boolean mVibrateOnOpening;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -880,7 +877,6 @@ public final class NotificationPanelViewController {
Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationListContainer notificationListContainer,
- PanelEventsEmitter panelEventsEmitter,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
@@ -993,7 +989,6 @@ public final class NotificationPanelViewController {
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
- mPanelEventsEmitter = panelEventsEmitter;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
mQs.animateHeaderSlidingOut();
@@ -1948,7 +1943,7 @@ public final class NotificationPanelViewController {
private void setQsExpandImmediate(boolean expandImmediate) {
if (expandImmediate != mQsExpandImmediate) {
mQsExpandImmediate = expandImmediate;
- mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
}
}
@@ -2674,8 +2669,8 @@ public final class NotificationPanelViewController {
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
- if (height == 0) {
- mCentralSurfaces.requestFaceAuth(false, FaceAuthApiRequestReason.QS_EXPANDED);
+ if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+ mUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
}
}
@@ -3889,7 +3884,7 @@ public final class NotificationPanelViewController {
boolean wasRunning = mIsLaunchAnimationRunning;
mIsLaunchAnimationRunning = running;
if (wasRunning != mIsLaunchAnimationRunning) {
- mPanelEventsEmitter.notifyLaunchingActivityChanged(running);
+ mShadeExpansionStateManager.notifyLaunchingActivityChanged(running);
}
}
@@ -3898,7 +3893,7 @@ public final class NotificationPanelViewController {
boolean wasClosing = isClosing();
mClosing = isClosing;
if (wasClosing != isClosing) {
- mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
+ mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
}
mAmbientState.setIsClosing(isClosing);
}
@@ -3933,7 +3928,7 @@ public final class NotificationPanelViewController {
mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
// Try triggering face auth, this "might" run. Check
// KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
- boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
if (didFaceAuthRun) {
@@ -4223,8 +4218,8 @@ public final class NotificationPanelViewController {
/**
* Sets the dozing state.
*
- * @param dozing {@code true} when dozing.
- * @param animate if transition should be animated.
+ * @param dozing {@code true} when dozing.
+ * @param animate if transition should be animated.
*/
public void setDozing(boolean dozing, boolean animate) {
if (dozing == mDozing) return;
@@ -4364,35 +4359,35 @@ public final class NotificationPanelViewController {
/**
* Starts fold to AOD animation.
*
- * @param startAction invoked when the animation starts.
- * @param endAction invoked when the animation finishes, also if it was cancelled.
+ * @param startAction invoked when the animation starts.
+ * @param endAction invoked when the animation finishes, also if it was cancelled.
* @param cancelAction invoked when the animation is cancelled, before endAction.
*/
public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
Runnable cancelAction) {
mView.animate()
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- startAction.run();
- }
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ startAction.run();
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- cancelAction.run();
- }
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ cancelAction.run();
+ }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
- }
- }).setUpdateListener(anim -> {
- mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
- }).start();
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ }).setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ }).start();
}
/**
@@ -4752,8 +4747,10 @@ public final class NotificationPanelViewController {
/**
* Maybe vibrate as panel is opened.
*
- * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
- * being opened programmatically (such as by the open panel gesture), we always play haptic.
+ * @param openingWithTouch Whether the panel is being opened with touch. If the panel is
+ * instead
+ * being opened programmatically (such as by the open panel gesture), we
+ * always play haptic.
*/
private void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening) {
@@ -4919,10 +4916,12 @@ public final class NotificationPanelViewController {
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
+
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
+
@Override
public void onAnimationEnd(Animator animation) {
mIsSpringBackAnimation = false;
@@ -4970,7 +4969,7 @@ public final class NotificationPanelViewController {
if (isNaN(h)) {
Log.wtf(TAG, "ExpandedHeight set to NaN");
}
- mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+ mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
if (mExpandLatencyTracking && h != 0f) {
DejankUtils.postAfterTraversal(
() -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
@@ -5161,7 +5160,7 @@ public final class NotificationPanelViewController {
/**
* Create an animator that can also overshoot
*
- * @param targetHeight the target height
+ * @param targetHeight the target height
* @param overshootAmount the amount of overshoot desired
*/
private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
@@ -5917,49 +5916,11 @@ public final class NotificationPanelViewController {
}
}
- @SysUISingleton
- static class PanelEventsEmitter implements NotifPanelEvents {
-
- private final ListenerSet<Listener> mListeners = new ListenerSet<>();
-
- @Inject
- PanelEventsEmitter() {
- }
-
- @Override
- public void registerListener(@androidx.annotation.NonNull @NonNull Listener listener) {
- mListeners.addIfAbsent(listener);
- }
-
- @Override
- public void unregisterListener(@androidx.annotation.NonNull @NonNull Listener listener) {
- mListeners.remove(listener);
- }
-
- private void notifyLaunchingActivityChanged(boolean isLaunchingActivity) {
- for (Listener cb : mListeners) {
- cb.onLaunchingActivityChanged(isLaunchingActivity);
- }
- }
-
- private void notifyPanelCollapsingChanged(boolean isCollapsing) {
- for (NotifPanelEvents.Listener cb : mListeners) {
- cb.onPanelCollapsingChanged(isCollapsing);
- }
- }
-
- private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
- for (NotifPanelEvents.Listener cb : mListeners) {
- cb.onExpandImmediateChanged(expandImmediateEnabled);
- }
- }
- }
-
/** Handles MotionEvents for the Shade. */
public final class TouchHandler implements View.OnTouchListener {
private long mLastTouchDownTime = -1L;
- /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
+ /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
public boolean onInterceptTouchEvent(MotionEvent event) {
if (SPEW_LOGCAT) {
Log.v(TAG,
@@ -6158,7 +6119,7 @@ public final class NotificationPanelViewController {
mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
return false;
}
- if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
return false;
}
@@ -6366,3 +6327,4 @@ public final class NotificationPanelViewController {
}
}
}
+
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
index 67723843086a..959c339ab3e5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
@@ -21,10 +21,9 @@ import com.android.systemui.dagger.SysUISingleton;
import dagger.Binds;
import dagger.Module;
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
+/** Provides a {@link ShadeStateEvents} in {@link SysUISingleton} scope. */
@Module
-public abstract class NotifPanelEventsModule {
+public abstract class ShadeEventsModule {
@Binds
- abstract NotifPanelEvents bindPanelEvents(
- NotificationPanelViewController.PanelEventsEmitter impl);
+ abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 7bba74a8b125..667392c9796e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -20,6 +20,7 @@ import android.annotation.IntDef
import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
import com.android.systemui.util.Compile
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
@@ -30,11 +31,12 @@ import javax.inject.Inject
* TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
*/
@SysUISingleton
-class ShadeExpansionStateManager @Inject constructor() {
+class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
+ private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
@PanelState private var state: Int = STATE_CLOSED
@FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
@@ -79,6 +81,14 @@ class ShadeExpansionStateManager @Inject constructor() {
stateListeners.remove(listener)
}
+ override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
+ shadeStateEventsListeners.addIfAbsent(listener)
+ }
+
+ override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) {
+ shadeStateEventsListeners.remove(listener)
+ }
+
/** Returns true if the panel is currently closed and false otherwise. */
fun isClosed(): Boolean = state == STATE_CLOSED
@@ -162,6 +172,24 @@ class ShadeExpansionStateManager @Inject constructor() {
stateListeners.forEach { it.onPanelStateChanged(state) }
}
+ fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onLaunchingActivityChanged(isLaunchingActivity)
+ }
+ }
+
+ fun notifyPanelCollapsingChanged(isCollapsing: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onPanelCollapsingChanged(isCollapsing)
+ }
+ }
+
+ fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) {
+ for (cb in shadeStateEventsListeners) {
+ cb.onExpandImmediateChanged(expandImmediateEnabled)
+ }
+ }
+
private fun debugLog(msg: String) {
if (!DEBUG) return
Log.v(TAG, msg)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
index 4558061de1a2..56bb1a6020cf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
@@ -16,27 +16,25 @@
package com.android.systemui.shade
-/** Provides certain notification panel events. */
-interface NotifPanelEvents {
+/** Provides certain notification panel events. */
+interface ShadeStateEvents {
- /** Registers callbacks to be invoked when notification panel events occur. */
- fun registerListener(listener: Listener)
+ /** Registers callbacks to be invoked when notification panel events occur. */
+ fun addShadeStateEventsListener(listener: ShadeStateEventsListener)
- /** Unregisters callbacks previously registered via [registerListener] */
- fun unregisterListener(listener: Listener)
+ /** Unregisters callbacks previously registered via [addShadeStateEventsListener] */
+ fun removeShadeStateEventsListener(listener: ShadeStateEventsListener)
/** Callbacks for certain notification panel events. */
- interface Listener {
+ interface ShadeStateEventsListener {
/** Invoked when the notification panel starts or stops collapsing. */
- @JvmDefault
- fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
+ @JvmDefault fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
/**
* Invoked when the notification panel starts or stops launching an [android.app.Activity].
*/
- @JvmDefault
- fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+ @JvmDefault fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
/**
* Invoked when the "expand immediate" attribute changes.
@@ -47,7 +45,6 @@ interface NotifPanelEvents {
* Another example is when full QS is showing, and we swipe up from the bottom. Instead of
* going to QQS, the panel fully collapses.
*/
- @JvmDefault
- fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
+ @JvmDefault fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index d3bc257d8a54..3002a6820990 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -28,7 +28,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -55,12 +55,12 @@ import javax.inject.Inject;
// TODO(b/204468557): Move to @CoordinatorScope
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator, Dumpable,
- NotifPanelEvents.Listener {
+ ShadeStateEvents.ShadeStateEventsListener {
public static final String TAG = "VisualStability";
public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private final DelayableExecutor mDelayableExecutor;
private final HeadsUpManager mHeadsUpManager;
- private final NotifPanelEvents mNotifPanelEvents;
+ private final ShadeStateEvents mShadeStateEvents;
private final StatusBarStateController mStatusBarStateController;
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -92,7 +92,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
DelayableExecutor delayableExecutor,
DumpManager dumpManager,
HeadsUpManager headsUpManager,
- NotifPanelEvents notifPanelEvents,
+ ShadeStateEvents shadeStateEvents,
StatusBarStateController statusBarStateController,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle) {
@@ -101,7 +101,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mDelayableExecutor = delayableExecutor;
- mNotifPanelEvents = notifPanelEvents;
+ mShadeStateEvents = shadeStateEvents;
dumpManager.registerDumpable(this);
}
@@ -114,7 +114,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
mPulsing = mStatusBarStateController.isPulsing();
- mNotifPanelEvents.registerListener(this);
+ mShadeStateEvents.addShadeStateEventsListener(this);
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
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 da4cceda531f..ff6389141575 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
@@ -32,8 +32,8 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.NotifPanelEventsModule;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeEventsModule;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
@@ -93,7 +93,7 @@ import dagger.Provides;
@Module(includes = {
CoordinatorsModule.class,
KeyguardNotificationVisibilityProviderModule.class,
- NotifPanelEventsModule.class,
+ ShadeEventsModule.class,
NotifPipelineChoreographerModule.class,
NotificationSectionHeadersModule.class,
})
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 70cf56d6d12c..2504fc1c7eb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -41,7 +41,6 @@ import androidx.lifecycle.LifecycleOwner;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.RemoteTransitionAdapter;
@@ -230,13 +229,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
boolean isShadeDisabled();
- /**
- * Request face auth to initiated
- * @param userInitiatedRequest Whether this was a user initiated request
- * @param reason Reason why face auth was triggered.
- */
- void requestFaceAuth(boolean userInitiatedRequest, @FaceAuthApiRequestReason String reason);
-
@Override
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
int flags);
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 29642beda53d..d227ed366ecb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -122,7 +122,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
@@ -1614,18 +1613,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
}
- /**
- * Asks {@link KeyguardUpdateMonitor} to run face auth.
- */
- @Override
- public void requestFaceAuth(boolean userInitiatedRequest,
- @FaceAuthApiRequestReason String reason) {
- if (!mKeyguardStateController.canDismissLockScreen()) {
- mKeyguardUpdateMonitor.requestFaceAuth(
- userInitiatedRequest, reason);
- }
- }
-
private void updateReportRejectedTouchVisibility() {
if (mReportRejectedTouch == null) {
return;
@@ -2960,7 +2947,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// * When phone is unlocked: we still don't want to execute hiding of the keyguard
// as the animation could prepare 'fake AOD' interface (without actually
// transitioning to keyguard state) and this might reset the view states
- if (!mScreenOffAnimationController.isKeyguardHideDelayed()) {
+ if (!mScreenOffAnimationController.isKeyguardHideDelayed()
+ // If we're animating occluded, there's an activity launching over the keyguard
+ // UI. Wait to hide it until after the animation concludes.
+ && !mKeyguardViewMediator.isOccludeAnimationPlaying()) {
return hideKeyguardImpl(forceStateChange);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 5e26cf062b58..4550cb2987ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -73,7 +73,6 @@ class KeyguardLiftController @Inject constructor(
isListening = false
updateListeningState()
keyguardUpdateMonitor.requestFaceAuth(
- true,
FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
)
keyguardUpdateMonitor.requestActiveUnlock(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 976710351a44..c189acec2930 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -649,37 +649,6 @@ public class NotificationIconContainer extends ViewGroup {
return mNumDots > 0;
}
- /**
- * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
- * extra padding will have to be accounted for
- *
- * This method has no meaning for non-static containers
- */
- public boolean hasPartialOverflow() {
- return mNumDots > 0 && mNumDots < MAX_DOTS;
- }
-
- /**
- * Get padding that can account for extra dots up to the max. The only valid values for
- * this method are for 1 or 2 dots.
- * @return only extraDotPadding or extraDotPadding * 2
- */
- public int getPartialOverflowExtraPadding() {
- if (!hasPartialOverflow()) {
- return 0;
- }
-
- int partialOverflowAmount = (MAX_DOTS - mNumDots) * (mStaticDotDiameter + mDotPadding);
-
- int adjustedWidth = getFinalTranslationX() + partialOverflowAmount;
- // In case we actually give too much padding...
- if (adjustedWidth > getWidth()) {
- partialOverflowAmount = getWidth() - getFinalTranslationX();
- }
-
- return partialOverflowAmount;
- }
-
// Give some extra room for btw notifications if we can
public int getNoOverflowExtraPadding() {
if (mNumDots != 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 84907686835b..cf3a48cf5000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -53,6 +53,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -204,6 +205,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardViewMediator mKeyguardViewMediator;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -273,7 +275,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ KeyguardViewMediator keyguardViewMediator) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
@@ -312,6 +315,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
});
mColors = new GradientColors();
+
+ mKeyguardViewMediator = keyguardViewMediator;
}
/**
@@ -807,6 +812,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mBehindTint,
interpolatedFraction);
}
+
+ // If we're unlocked but still playing the occlude animation, remain at the keyguard
+ // alpha temporarily.
+ if (mKeyguardViewMediator.isOccludeAnimationPlaying()
+ || mState.mLaunchingAffordanceWithPreview) {
+ mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
+ }
} else if (mState == ScrimState.AUTH_SCRIMMED_SHADE) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index a0415f2f3d7c..6cd8c78dd52f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -22,8 +22,6 @@ import android.view.WindowInsetsController.Behavior
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
@@ -42,7 +40,6 @@ class SystemBarAttributesListener
@Inject
internal constructor(
private val centralSurfaces: CentralSurfaces,
- private val featureFlags: FeatureFlags,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
private val statusBarStateController: SysuiStatusBarStateController,
private val lightBarController: LightBarController,
@@ -127,15 +124,11 @@ internal constructor(
}
private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) =
- isLetterboxAppearanceFlagEnabled() && letterboxDetails.isNotEmpty()
-
- private fun isLetterboxAppearanceFlagEnabled() =
- featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)
+ letterboxDetails.isNotEmpty()
private fun dump(printWriter: PrintWriter, strings: Array<String>) {
printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams")
printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance")
- printWriter.println("letterbox appearance flag: ${isLetterboxAppearanceFlagEnabled()}")
}
}
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 2aaa085645e4..fcd1b8abefe4 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
@@ -18,10 +18,14 @@ package com.android.systemui.statusbar.pipeline.dagger
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepositoryImpl
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepositoryImpl
+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.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.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
@@ -41,10 +45,16 @@ abstract class StatusBarPipelineModule {
abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
- abstract fun mobileSubscriptionRepository(
- impl: MobileSubscriptionRepositoryImpl
- ): MobileSubscriptionRepository
+ abstract fun mobileConnectionsRepository(
+ impl: MobileConnectionsRepositoryImpl
+ ): MobileConnectionsRepository
@Binds
abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+
+ @Binds
+ abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+
+ @Binds
+ abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
}
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/MobileSubscriptionModel.kt
index 46ccf32cc7f9..eaba0e93e750 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/MobileSubscriptionModel.kt
@@ -27,6 +27,7 @@ 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
/**
* Data class containing all of the relevant information for a particular line of service, known as
@@ -57,6 +58,11 @@ data class MobileSubscriptionModel(
/** From [CarrierNetworkListener.onCarrierNetworkChange] */
val carrierNetworkChangeActive: Boolean? = null,
- /** From [DisplayInfoListener.onDisplayInfoChanged] */
- val displayInfo: TelephonyDisplayInfo? = null
+ /**
+ * From [DisplayInfoListener.onDisplayInfoChanged].
+ *
+ * [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),
)
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
new file mode 100644
index 000000000000..f385806c1b22
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.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.statusbar.pipeline.mobile.data.model
+
+import android.telephony.Annotation.NetworkType
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+
+/**
+ * A SysUI type to represent the [NetworkType] that we pull out of [TelephonyDisplayInfo]. Depending
+ * on whether or not the display info contains an override type, we may have to call different
+ * methods on [MobileMappingsProxy] to generate an icon lookup key.
+ */
+sealed interface ResolvedNetworkType {
+ @NetworkType val type: Int
+}
+
+data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
+
+data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 36de2a254160..45284cf0332b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -21,23 +21,18 @@ 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.TelephonyCallback.ActiveDataSubscriptionIdListener
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
import android.telephony.TelephonyManager
-import androidx.annotation.VisibleForTesting
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.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.shared.ConnectivityPipelineLogger
+import java.lang.IllegalStateException
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -47,110 +42,64 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.mapLatest
+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
- * on various policy
+ * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
+ * repository for each individual, tracked subscription via [MobileConnectionsRepository], and this
+ * repository is responsible for setting up a [TelephonyManager] object tied to its subscriptionId
+ *
+ * There should only ever be one [MobileConnectionRepository] per subscription, since
+ * [TelephonyManager] limits the number of callbacks that can be registered per process.
+ *
+ * This repository should have all of the relevant information for a single line of service, which
+ * eventually becomes a single icon in the status bar.
*/
-interface MobileSubscriptionRepository {
- /** Observable list of current mobile subscriptions */
- val subscriptionsFlow: Flow<List<SubscriptionInfo>>
-
- /** Observable for the subscriptionId of the current mobile data connection */
- val activeMobileDataSubscriptionId: Flow<Int>
-
- /** Get or create an observable for the given subscription ID */
- fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel>
+interface MobileConnectionRepository {
+ /**
+ * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
+ * listener + model.
+ */
+ val subscriptionModelFlow: Flow<MobileSubscriptionModel>
}
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MobileSubscriptionRepositoryImpl
-@Inject
-constructor(
- private val subscriptionManager: SubscriptionManager,
- private val telephonyManager: TelephonyManager,
- @Background private val bgDispatcher: CoroutineDispatcher,
- @Application private val scope: CoroutineScope,
-) : MobileSubscriptionRepository {
- private val subIdFlowCache: MutableMap<Int, StateFlow<MobileSubscriptionModel>> = 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() }
- .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(),
- SubscriptionManager.INVALID_SUBSCRIPTION_ID
+class MobileConnectionRepositoryImpl(
+ private val subId: Int,
+ telephonyManager: TelephonyManager,
+ 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."
)
-
- /**
- * Each mobile subscription needs its own flow, which comes from registering listeners on the
- * system. Use this method to create those flows and cache them for reuse
- */
- override fun getFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> {
- return subIdFlowCache[subId]
- ?: createFlowForSubId(subId).also { subIdFlowCache[subId] = it }
+ }
}
- @VisibleForTesting fun getSubIdFlowCache() = subIdFlowCache
-
- private fun createFlowForSubId(subId: Int): StateFlow<MobileSubscriptionModel> = run {
+ override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
var state = MobileSubscriptionModel()
conflatedCallbackFlow {
- val phony = telephonyManager.createForSubscriptionId(subId)
// TODO (b/240569788): log all of these into the connectivity logger
val callback =
object :
TelephonyCallback(),
- ServiceStateListener,
- SignalStrengthsListener,
- DataConnectionStateListener,
- DataActivityListener,
- CarrierNetworkListener,
- DisplayInfoListener {
+ 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
@@ -173,6 +122,7 @@ constructor(
)
trySend(state)
}
+
override fun onDataConnectionStateChanged(
dataState: Int,
networkType: Int
@@ -180,31 +130,56 @@ constructor(
state = state.copy(dataConnectionState = dataState)
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
) {
- state = state.copy(displayInfo = telephonyDisplayInfo)
+ val networkType =
+ if (
+ telephonyDisplayInfo.overrideNetworkType ==
+ OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(telephonyDisplayInfo.networkType)
+ } else {
+ OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
+ }
+ state = state.copy(resolvedNetworkType = networkType)
trySend(state)
}
}
- phony.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
- awaitClose {
- phony.unregisterTelephonyCallback(callback)
- // Release the cached flow
- subIdFlowCache.remove(subId)
- }
+ telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+ awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
}
+ .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
.stateIn(scope, SharingStarted.WhileSubscribed(), state)
}
- private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
- withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+ class Factory
+ @Inject
+ constructor(
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ ) {
+ fun build(subId: Int): MobileConnectionRepository {
+ return MobileConnectionRepositoryImpl(
+ subId,
+ telephonyManager.createForSubscriptionId(subId),
+ 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
new file mode 100644
index 000000000000..0e2428ae393a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -0,0 +1,201 @@
+/*
+ * 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.content.Context
+import android.content.IntentFilter
+import android.telephony.CarrierConfigManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyManager
+import androidx.annotation.VisibleForTesting
+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.shared.ConnectivityPipelineLogger
+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.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapLatest
+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
+ * on various policy
+ */
+interface MobileConnectionsRepository {
+ /** Observable list of current mobile subscriptions */
+ val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+
+ /** Observable for the subscriptionId of the current mobile data connection */
+ val activeMobileDataSubscriptionId: Flow<Int>
+
+ /** Observable for [MobileMappings.Config] tracking the defaults */
+ val defaultDataSubRatConfig: StateFlow<Config>
+
+ /** Get or create a repository for the line of service for the given subscription ID */
+ fun getRepoForSubId(subId: Int): MobileConnectionRepository
+}
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileConnectionsRepositoryImpl
+@Inject
+constructor(
+ private val subscriptionManager: SubscriptionManager,
+ private val telephonyManager: TelephonyManager,
+ private val logger: ConnectivityPipelineLogger,
+ broadcastDispatcher: BroadcastDispatcher,
+ 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(),
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ )
+
+ private val defaultDataSubChangedEvent =
+ broadcastDispatcher.broadcastFlow(
+ IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+ )
+
+ 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> =
+ combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
+ 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 }
+ }
+
+ 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)
+ }
+
+ 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)
+ }
+ }
+ }
+
+ private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+ withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+}
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 40fe0f3e8fe0..15f4acc1127c 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
@@ -17,32 +17,58 @@
package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
-import com.android.settingslib.SignalIcon
-import com.android.settingslib.mobile.TelephonyIcons
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.settingslib.SignalIcon.MobileIconGroup
+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.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
interface MobileIconInteractor {
- /** Identifier for RAT type indicator */
- val iconGroup: Flow<SignalIcon.MobileIconGroup>
+ /** Observable for RAT type (network type) indicator */
+ val networkTypeIconGroup: Flow<MobileIconGroup>
+
/** True if this line of service is emergency-only */
val isEmergencyOnly: Flow<Boolean>
+
/** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
val level: Flow<Int>
+
/** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
val numberOfLevels: Flow<Int>
+
/** True when we want to draw an icon that makes room for the exclamation mark */
val cutOut: Flow<Boolean>
}
/** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
class MobileIconInteractorImpl(
- mobileStatusInfo: Flow<MobileSubscriptionModel>,
+ defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
+ defaultMobileIconGroup: Flow<MobileIconGroup>,
+ mobileMappingsProxy: MobileMappingsProxy,
+ connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
- override val iconGroup: Flow<SignalIcon.MobileIconGroup> = flowOf(TelephonyIcons.THREE_G)
+ private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
+
+ /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
+ override val networkTypeIconGroup: Flow<MobileIconGroup> =
+ combine(
+ mobileStatusInfo,
+ 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
+ }
+
override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
override val level: Flow<Int> =
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 8e67e19f3e35..cd411a4a2afe 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
@@ -19,29 +19,51 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CarrierConfigManager
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileSubscriptionRepository
+import com.android.systemui.dagger.qualifiers.Application
+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
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
- * Business layer logic for mobile subscription icons
+ * Business layer logic for the set of mobile subscription icons.
*
- * Mobile indicators represent the UI for the (potentially filtered) list of [SubscriptionInfo]s
- * that the system knows about. They obey policy that depends on OEM, carrier, and locale configs
+ * This interactor represents known set of mobile subscriptions (represented by [SubscriptionInfo]).
+ * The list of subscriptions is filtered based on the opportunistic flags on the infos.
+ *
+ * It provides the default mapping between the telephony display info and the icon group that
+ * represents each RAT (LTE, 3G, etc.), as well as can produce an interactor for each individual
+ * icon
*/
+interface MobileIconsInteractor {
+ val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+ val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
+ val defaultMobileIconGroup: Flow<MobileIconGroup>
+ val isUserSetup: Flow<Boolean>
+ fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
+}
+
@SysUISingleton
-class MobileIconsInteractor
+class MobileIconsInteractorImpl
@Inject
constructor(
- private val mobileSubscriptionRepo: MobileSubscriptionRepository,
+ private val mobileSubscriptionRepo: MobileConnectionsRepository,
private val carrierConfigTracker: CarrierConfigTracker,
+ private val mobileMappingsProxy: MobileMappingsProxy,
userSetupRepo: UserSetupRepository,
-) {
+ @Application private val scope: CoroutineScope,
+) : MobileIconsInteractor {
private val activeMobileDataSubscriptionId =
mobileSubscriptionRepo.activeMobileDataSubscriptionId
@@ -61,7 +83,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.
*/
- val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+ override val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
->
// Based on the old logic,
@@ -92,15 +114,29 @@ constructor(
}
}
- val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
-
- /** Vends out new [MobileIconInteractor] for a particular subId */
- fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
- MobileIconInteractorImpl(mobileSubscriptionFlowForSubId(subId))
-
/**
- * Create a new flow for a given subscription ID, which usually maps 1:1 with mobile connections
+ * Mapping from network type to [MobileIconGroup] using the config generated for the default
+ * subscription Id. This mapping is the same for every subscription.
*/
- private fun mobileSubscriptionFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> =
- mobileSubscriptionRepo.getFlowForSubId(subId)
+ override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
+ mobileSubscriptionRepo.defaultDataSubRatConfig
+ .map { mobileMappingsProxy.mapIconSets(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
+
+ /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
+ override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
+ mobileSubscriptionRepo.defaultDataSubRatConfig
+ .map { mobileMappingsProxy.getDefaultIcons(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
+
+ override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+
+ /** Vends out new [MobileIconInteractor] for a particular subId */
+ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
+ MobileIconInteractorImpl(
+ defaultMobileIconMapping,
+ defaultMobileIconGroup,
+ mobileMappingsProxy,
+ mobileSubscriptionRepo.getRepoForSubId(subId),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 1405b050234b..67ea139271fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.binder
import android.content.res.ColorStateList
+import android.view.View.GONE
+import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.isVisible
@@ -24,6 +26,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.R
+import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import kotlinx.coroutines.flow.collect
@@ -37,6 +40,7 @@ object MobileIconBinder {
view: ViewGroup,
viewModel: MobileIconViewModel,
) {
+ val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
@@ -52,10 +56,20 @@ object MobileIconBinder {
}
}
+ // Set the network type icon
+ launch {
+ viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
+ networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
+ }
+ }
+
// Set the tint
launch {
viewModel.tint.collect { tint ->
- iconView.imageTintList = ColorStateList.valueOf(tint)
+ val tintList = ColorStateList.valueOf(tint)
+ iconView.imageTintList = tintList
+ networkTypeView.imageTintList = tintList
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index cfabeba8432c..cc8f6dd08585 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import android.graphics.Color
import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
@@ -26,6 +28,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/**
* View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -54,5 +57,15 @@ constructor(
.distinctUntilChanged()
.logOutputChange(logger, "iconId($subscriptionId)")
+ /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
+ var networkTypeIcon: Flow<Icon?> =
+ iconInteractor.networkTypeIconGroup.map {
+ val desc =
+ if (it.dataContentDescription != 0)
+ ContentDescription.Resource(it.dataContentDescription)
+ else null
+ Icon.Resource(it.dataType, desc)
+ }
+
var tint: Flow<Int> = flowOf(Color.CYAN)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
new file mode 100644
index 000000000000..60bd0383f8c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/MobileMappings.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.util
+
+import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyDisplayInfo
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import javax.inject.Inject
+
+/**
+ * [MobileMappings] owns the logic on creating the map from [TelephonyDisplayInfo] to
+ * [MobileIconGroup]. It creates that hash map and also manages the creation of lookup keys. This
+ * interface allows us to proxy those calls to the static java methods in SettingsLib and also fake
+ * them out in tests
+ */
+interface MobileMappingsProxy {
+ fun mapIconSets(config: Config): Map<String, MobileIconGroup>
+ fun getDefaultIcons(config: Config): MobileIconGroup
+ fun toIconKey(@NetworkType networkType: Int): String
+ fun toIconKeyOverride(@NetworkType networkType: Int): String
+}
+
+/** Injectable wrapper class for [MobileMappings] */
+class MobileMappingsProxyImpl @Inject constructor() : MobileMappingsProxy {
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> =
+ MobileMappings.mapIconSets(config)
+
+ override fun getDefaultIcons(config: Config): MobileIconGroup =
+ MobileMappings.getDefaultIcons(config)
+
+ override fun toIconKey(@NetworkType networkType: Int): String =
+ MobileMappings.toIconKey(networkType)
+
+ override fun toIconKeyOverride(networkType: Int): String =
+ MobileMappings.toDisplayIconKey(networkType)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 28a9b97b8ea6..cf4106c508cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -61,7 +61,7 @@ protected constructor(
* animation to and from the parent dialog.
*/
@JvmOverloads
- fun onUserListItemClicked(
+ open fun onUserListItemClicked(
record: UserRecord,
dialogShower: DialogShower? = null,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index f0a50de02b3a..637fac05f0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -44,11 +44,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor
*
* The generic type T is expected to contain all the information necessary for the subclasses to
* display the view in a certain state, since they receive <T> in [updateView].
- *
- * @property windowTitle the title to use for the window that displays the temporary view. Should be
- * normally cased, like "Window Title".
- * @property wakeReason a string used for logging if we needed to wake the screen in order to
- * display the temporary view. Should be screaming snake cased, like WAKE_REASON.
*/
abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
internal val context: Context,
@@ -59,8 +54,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
private val configurationController: ConfigurationController,
private val powerManager: PowerManager,
@LayoutRes private val viewLayoutRes: Int,
- private val windowTitle: String,
- private val wakeReason: String,
) : CoreStartable {
/**
* Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -72,7 +65,6 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- title = windowTitle
format = PixelFormat.TRANSLUCENT
setTrustedOverlay()
}
@@ -100,29 +92,40 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
fun displayView(newInfo: T) {
val currentDisplayInfo = displayInfo
- if (currentDisplayInfo != null) {
+ if (currentDisplayInfo != null &&
+ currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
+ // We're already displaying information in the correctly-titled window, so we just need
+ // to update the view.
currentDisplayInfo.info = newInfo
updateView(currentDisplayInfo.info, currentDisplayInfo.view)
} else {
- // The view is new, so set up all our callbacks and inflate the view
+ if (currentDisplayInfo != null) {
+ // We're already displaying information but that information is under a different
+ // window title. So, we need to remove the old window with the old title and add a
+ // new window with the new title.
+ removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}")
+ }
+
+ // At this point, we're guaranteed to no longer be displaying a view.
+ // So, set up all our callbacks and inflate the view.
configurationController.addCallback(displayScaleListener)
// Wake the screen if necessary so the user will see the view. (Per b/239426653, we want
// the view to show over the dream state, so we should only wake up if the screen is
// completely off.)
if (!powerManager.isScreenOn) {
powerManager.wakeUp(
- SystemClock.uptimeMillis(),
- PowerManager.WAKE_REASON_APPLICATION,
- "com.android.systemui:$wakeReason",
+ SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_APPLICATION,
+ "com.android.systemui:${newInfo.wakeReason}",
)
}
- logger.logChipAddition()
+ logger.logViewAddition(newInfo.windowTitle)
inflateAndUpdateView(newInfo)
}
// Cancel and re-set the view timeout each time we get a new state.
val timeout = accessibilityManager.getRecommendedTimeoutMillis(
- newInfo.getTimeoutMs().toInt(),
+ newInfo.timeoutMs,
// Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
// include it just to be safe.
FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -147,7 +150,12 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
val newDisplayInfo = DisplayInfo(newView, newInfo)
displayInfo = newDisplayInfo
updateView(newDisplayInfo.info, newDisplayInfo.view)
- windowManager.addView(newView, windowLayoutParams)
+
+ val paramsWithTitle = WindowManager.LayoutParams().also {
+ it.copyFrom(windowLayoutParams)
+ it.title = newInfo.windowTitle
+ }
+ windowManager.addView(newView, paramsWithTitle)
animateViewIn(newView)
}
@@ -177,7 +185,7 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
val currentView = currentDisplayInfo.view
animateViewOut(currentView) { windowManager.removeView(currentView) }
- logger.logChipRemoval(removalReason)
+ logger.logViewRemoval(removalReason)
configurationController.removeCallback(displayScaleListener)
// Re-set to null immediately (instead as part of the animation end runnable) so
// that if a new view event comes in while this view is animating out, we still display the
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index 4fe753a80faf..cbb500296888 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -19,12 +19,24 @@ package com.android.systemui.temporarydisplay
/**
* A superclass view state used with [TemporaryViewDisplayController].
*/
-interface TemporaryViewInfo {
+abstract class TemporaryViewInfo {
/**
- * Returns the amount of time the given view state should display on the screen before it times
- * out and disappears.
+ * The title to use for the window that displays the temporary view. Should be normally cased,
+ * like "Window Title".
*/
- fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
+ abstract val windowTitle: String
+
+ /**
+ * A string used for logging if we needed to wake the screen in order to display the temporary
+ * view. Should be screaming snake cased, like WAKE_REASON.
+ */
+ abstract val wakeReason: String
+
+ /**
+ * The amount of time the given view state should display on the screen before it times out and
+ * disappears.
+ */
+ open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
}
-const val DEFAULT_TIMEOUT_MILLIS = 10000L
+const val DEFAULT_TIMEOUT_MILLIS = 10000
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index a7185cb18c40..428a104484a7 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -24,13 +24,13 @@ open class TemporaryViewLogger(
internal val buffer: LogBuffer,
internal val tag: String,
) {
- /** Logs that we added the chip to a new window. */
- fun logChipAddition() {
- buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" })
+ /** Logs that we added the view in a window titled [windowTitle]. */
+ fun logViewAddition(windowTitle: String) {
+ buffer.log(tag, LogLevel.DEBUG, { str1 = windowTitle }, { "View added. window=$str1" })
}
/** Logs that we removed the chip for the given [reason]. */
- fun logChipRemoval(reason: String) {
- buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" })
+ fun logViewRemoval(reason: String) {
+ buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" })
}
}
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 cd7bd2dae8fd..87b6e8d3af34 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -38,9 +38,6 @@ import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.common.MediaTttUtils
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,14 +61,11 @@ import javax.inject.Inject
* Only one chipbar may be shown at a time.
* TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
* need to maintain a priority ordering?
- *
- * TODO(b/245610654): Remove all media-related items from this class so it's just for generic
- * chipbars.
*/
@SysUISingleton
open class ChipbarCoordinator @Inject constructor(
context: Context,
- @MediaTttSenderLogger logger: MediaTttLogger,
+ logger: ChipbarLogger,
windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
@@ -81,7 +75,7 @@ open class ChipbarCoordinator @Inject constructor(
private val falsingCollector: FalsingCollector,
private val viewUtil: ViewUtil,
private val vibratorHelper: VibratorHelper,
-) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
+) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
windowManager,
@@ -90,8 +84,6 @@ open class ChipbarCoordinator @Inject constructor(
configurationController,
powerManager,
R.layout.chipbar,
- MediaTttUtils.WINDOW_TITLE,
- MediaTttUtils.WAKE_REASON,
) {
private lateinit var parent: ChipbarRootView
@@ -106,7 +98,16 @@ open class ChipbarCoordinator @Inject constructor(
newInfo: ChipbarInfo,
currentView: ViewGroup
) {
- // TODO(b/245610654): Adding logging here.
+ logger.logViewUpdate(
+ newInfo.windowTitle,
+ newInfo.text.loadText(context),
+ when (newInfo.endItem) {
+ null -> "null"
+ is ChipbarEndItem.Loading -> "loading"
+ is ChipbarEndItem.Error -> "error"
+ is ChipbarEndItem.Button -> "button(${newInfo.endItem.text.loadText(context)})"
+ }
+ )
// Detect falsing touches on the chip.
parent = currentView.requireViewById(R.id.chipbar_root_view)
@@ -204,5 +205,4 @@ open class ChipbarCoordinator @Inject constructor(
}
}
-const val SENDER_TAG = "MediaTapToTransferSender"
private const val ANIMATION_DURATION = 500L
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 57fde87114d0..6237365d0cee 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -37,7 +37,10 @@ data class ChipbarInfo(
val text: Text,
val endItem: ChipbarEndItem?,
val vibrationEffect: VibrationEffect? = null,
-) : TemporaryViewInfo
+ override val windowTitle: String,
+ override val wakeReason: String,
+ override val timeoutMs: Int,
+) : TemporaryViewInfo()
/** The possible items to display at the end of the chipbar. */
sealed class ChipbarEndItem {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
new file mode 100644
index 000000000000..e477cd68673a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -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 com.android.systemui.temporarydisplay.chipbar
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewLogger
+import com.android.systemui.temporarydisplay.dagger.ChipbarLog
+import javax.inject.Inject
+
+/** A logger for the chipbar. */
+@SysUISingleton
+class ChipbarLogger
+@Inject
+constructor(
+ @ChipbarLog buffer: LogBuffer,
+) : TemporaryViewLogger(buffer, "ChipbarLog") {
+ /**
+ * Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
+ * [endItemDesc].
+ */
+ fun logViewUpdate(windowTitle: String, text: String?, endItemDesc: String) {
+ buffer.log(
+ tag,
+ LogLevel.DEBUG,
+ {
+ str1 = windowTitle
+ str2 = text
+ str3 = endItemDesc
+ },
+ { "Chipbar updated. window=$str1 text=$str2 endItem=$str3" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
new file mode 100644
index 000000000000..5f101f2f388d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.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.temporarydisplay.dagger
+
+import javax.inject.Qualifier
+
+/** Status bar connectivity logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class ChipbarLog
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
new file mode 100644
index 000000000000..cf0a1835c8e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.temporarydisplay.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.plugins.log.LogBuffer
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface TemporaryDisplayModule {
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ @ChipbarLog
+ fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("ChipbarLog", 40)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
new file mode 100644
index 000000000000..9653985cb6e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.kotlin
+
+import android.view.View
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.lifecycle.repeatWhenAttached
+import java.util.function.Consumer
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collect
+
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event. Defaults to
+ * [LifeCycle.State.CREATED] to better align with legacy ViewController usage of attaching listeners
+ * during onViewAttached() and removing during onViewRemoved()
+ */
+@JvmOverloads
+fun <T> collectFlow(
+ view: View,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+ state: Lifecycle.State = Lifecycle.State.CREATED,
+) {
+ view.repeatWhenAttached { repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 42d7d52a71ab..44f6d03207b1 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -47,7 +47,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.wallpapers.canvas.WallpaperColorExtractor;
+import com.android.systemui.wallpapers.canvas.WallpaperLocalColorExtractor;
import com.android.systemui.wallpapers.gl.EglHelper;
import com.android.systemui.wallpapers.gl.ImageWallpaperRenderer;
@@ -521,7 +521,7 @@ public class ImageWallpaper extends WallpaperService {
class CanvasEngine extends WallpaperService.Engine implements DisplayListener {
private WallpaperManager mWallpaperManager;
- private final WallpaperColorExtractor mWallpaperColorExtractor;
+ private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@@ -543,9 +543,9 @@ public class ImageWallpaper extends WallpaperService {
super();
setFixedSizeAllowed(true);
setShowForAllUsers(true);
- mWallpaperColorExtractor = new WallpaperColorExtractor(
+ mWallpaperLocalColorExtractor = new WallpaperLocalColorExtractor(
mBackgroundExecutor,
- new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -570,7 +570,7 @@ public class ImageWallpaper extends WallpaperService {
// if the number of pages is already computed, transmit it to the color extractor
if (mPagesComputed) {
- mWallpaperColorExtractor.onPageChanged(mPages);
+ mWallpaperLocalColorExtractor.onPageChanged(mPages);
}
}
@@ -597,7 +597,7 @@ public class ImageWallpaper extends WallpaperService {
public void onDestroy() {
getDisplayContext().getSystemService(DisplayManager.class)
.unregisterDisplayListener(this);
- mWallpaperColorExtractor.cleanUp();
+ mWallpaperLocalColorExtractor.cleanUp();
unloadBitmap();
}
@@ -813,7 +813,7 @@ public class ImageWallpaper extends WallpaperService {
@VisibleForTesting
void recomputeColorExtractorMiniBitmap() {
- mWallpaperColorExtractor.onBitmapChanged(mBitmap);
+ mWallpaperLocalColorExtractor.onBitmapChanged(mBitmap);
}
@VisibleForTesting
@@ -830,14 +830,14 @@ public class ImageWallpaper extends WallpaperService {
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
// this call will activate the offset notifications
// if no colors were being processed before
- mWallpaperColorExtractor.addLocalColorsAreas(regions);
+ mWallpaperLocalColorExtractor.addLocalColorsAreas(regions);
}
@Override
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
// this call will deactivate the offset notifications
// if we are no longer processing colors
- mWallpaperColorExtractor.removeLocalColorAreas(regions);
+ mWallpaperLocalColorExtractor.removeLocalColorAreas(regions);
}
@Override
@@ -853,7 +853,7 @@ public class ImageWallpaper extends WallpaperService {
if (pages != mPages || !mPagesComputed) {
mPages = pages;
mPagesComputed = true;
- mWallpaperColorExtractor.onPageChanged(mPages);
+ mWallpaperLocalColorExtractor.onPageChanged(mPages);
}
}
@@ -881,7 +881,7 @@ public class ImageWallpaper extends WallpaperService {
.getSystemService(WindowManager.class)
.getCurrentWindowMetrics()
.getBounds();
- mWallpaperColorExtractor.setDisplayDimensions(window.width(), window.height());
+ mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height());
}
@@ -902,7 +902,7 @@ public class ImageWallpaper extends WallpaperService {
: mBitmap.isRecycled() ? "recycled"
: mBitmap.getWidth() + "x" + mBitmap.getHeight());
- mWallpaperColorExtractor.dump(prefix, fd, out, args);
+ mWallpaperLocalColorExtractor.dump(prefix, fd, out, args);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
index e2e4555bb965..6cac5c952b7c 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractor.java
@@ -45,14 +45,14 @@ import java.util.concurrent.Executor;
* It uses a background executor, and uses callbacks to inform that the work is done.
* It uses a downscaled version of the wallpaper to extract the colors.
*/
-public class WallpaperColorExtractor {
+public class WallpaperLocalColorExtractor {
private Bitmap mMiniBitmap;
@VisibleForTesting
static final int SMALL_SIDE = 128;
- private static final String TAG = WallpaperColorExtractor.class.getSimpleName();
+ private static final String TAG = WallpaperLocalColorExtractor.class.getSimpleName();
private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
new RectF(0, 0, 1, 1);
@@ -70,12 +70,12 @@ public class WallpaperColorExtractor {
@Background
private final Executor mBackgroundExecutor;
- private final WallpaperColorExtractorCallback mWallpaperColorExtractorCallback;
+ private final WallpaperLocalColorExtractorCallback mWallpaperLocalColorExtractorCallback;
/**
* Interface to handle the callbacks after the different steps of the color extraction
*/
- public interface WallpaperColorExtractorCallback {
+ public interface WallpaperLocalColorExtractorCallback {
/**
* Callback after the colors of new regions have been extracted
* @param regions the list of new regions that have been processed
@@ -103,13 +103,13 @@ public class WallpaperColorExtractor {
/**
* Creates a new color extractor.
* @param backgroundExecutor the executor on which the color extraction will be performed
- * @param wallpaperColorExtractorCallback an interface to handle the callbacks from
+ * @param wallpaperLocalColorExtractorCallback an interface to handle the callbacks from
* the color extractor.
*/
- public WallpaperColorExtractor(@Background Executor backgroundExecutor,
- WallpaperColorExtractorCallback wallpaperColorExtractorCallback) {
+ public WallpaperLocalColorExtractor(@Background Executor backgroundExecutor,
+ WallpaperLocalColorExtractorCallback wallpaperLocalColorExtractorCallback) {
mBackgroundExecutor = backgroundExecutor;
- mWallpaperColorExtractorCallback = wallpaperColorExtractorCallback;
+ mWallpaperLocalColorExtractorCallback = wallpaperLocalColorExtractorCallback;
}
/**
@@ -157,7 +157,7 @@ public class WallpaperColorExtractor {
mBitmapWidth = bitmap.getWidth();
mBitmapHeight = bitmap.getHeight();
mMiniBitmap = createMiniBitmap(bitmap);
- mWallpaperColorExtractorCallback.onMiniBitmapUpdated();
+ mWallpaperLocalColorExtractorCallback.onMiniBitmapUpdated();
recomputeColors();
}
}
@@ -206,7 +206,7 @@ public class WallpaperColorExtractor {
boolean wasActive = isActive();
mPendingRegions.addAll(regions);
if (!wasActive && isActive()) {
- mWallpaperColorExtractorCallback.onActivated();
+ mWallpaperLocalColorExtractorCallback.onActivated();
}
processColorsInternal();
}
@@ -228,7 +228,7 @@ public class WallpaperColorExtractor {
mPendingRegions.removeAll(regions);
regions.forEach(mProcessedRegions::remove);
if (wasActive && !isActive()) {
- mWallpaperColorExtractorCallback.onDeactivated();
+ mWallpaperLocalColorExtractorCallback.onDeactivated();
}
}
}
@@ -252,7 +252,7 @@ public class WallpaperColorExtractor {
}
private Bitmap createMiniBitmap(@NonNull Bitmap bitmap) {
- Trace.beginSection("WallpaperColorExtractor#createMiniBitmap");
+ Trace.beginSection("WallpaperLocalColorExtractor#createMiniBitmap");
// if both sides of the image are larger than SMALL_SIDE, downscale the bitmap.
int smallestSide = Math.min(bitmap.getWidth(), bitmap.getHeight());
float scale = Math.min(1.0f, (float) SMALL_SIDE / smallestSide);
@@ -359,7 +359,7 @@ public class WallpaperColorExtractor {
*/
if (mDisplayWidth < 0 || mDisplayHeight < 0 || mPages < 0) return;
- Trace.beginSection("WallpaperColorExtractor#processColorsInternal");
+ Trace.beginSection("WallpaperLocalColorExtractor#processColorsInternal");
List<WallpaperColors> processedColors = new ArrayList<>();
for (int i = 0; i < mPendingRegions.size(); i++) {
RectF nextArea = mPendingRegions.get(i);
@@ -372,7 +372,7 @@ public class WallpaperColorExtractor {
mPendingRegions.clear();
Trace.endSection();
- mWallpaperColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
+ mWallpaperLocalColorExtractorCallback.onColorsProcessed(processedRegions, processedColors);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 309f1681b964..02738d5ae48b 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -49,6 +49,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
@@ -58,7 +59,6 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -113,7 +113,6 @@ public final class WMShell implements
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<FloatingTasks> mFloatingTasksOptional;
private final Optional<DesktopMode> mDesktopModeOptional;
private final CommandQueue mCommandQueue;
@@ -125,6 +124,7 @@ public final class WMShell implements
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final ProtoTracer mProtoTracer;
private final UserTracker mUserTracker;
+ private final NoteTaskInitializer mNoteTaskInitializer;
private final Executor mSysUiMainExecutor;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
@@ -176,7 +176,6 @@ public final class WMShell implements
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- Optional<FloatingTasks> floatingTasksOptional,
Optional<DesktopMode> desktopMode,
CommandQueue commandQueue,
ConfigurationController configurationController,
@@ -187,6 +186,7 @@ public final class WMShell implements
ProtoTracer protoTracer,
WakefulnessLifecycle wakefulnessLifecycle,
UserTracker userTracker,
+ NoteTaskInitializer noteTaskInitializer,
@Main Executor sysUiMainExecutor) {
mContext = context;
mShell = shell;
@@ -203,7 +203,7 @@ public final class WMShell implements
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
mUserTracker = userTracker;
- mFloatingTasksOptional = floatingTasksOptional;
+ mNoteTaskInitializer = noteTaskInitializer;
mSysUiMainExecutor = sysUiMainExecutor;
}
@@ -226,6 +226,8 @@ public final class WMShell implements
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
mDesktopModeOptional.ifPresent(this::initDesktopMode);
+
+ mNoteTaskInitializer.initialize();
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
new file mode 100644
index 000000000000..6c5620d42abb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.keyguard
+
+import android.os.PowerManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.GlobalSettings
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceWakeUpTriggersConfigTest : SysuiTestCase() {
+ @Mock lateinit var globalSettings: GlobalSettings
+ @Mock lateinit var dumpManager: DumpManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testShouldTriggerFaceAuthOnWakeUpFrom_inConfig_returnsTrue() {
+ val faceWakeUpTriggersConfig =
+ createFaceWakeUpTriggersConfig(
+ intArrayOf(PowerManager.WAKE_REASON_POWER_BUTTON, PowerManager.WAKE_REASON_GESTURE)
+ )
+
+ assertTrue(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_POWER_BUTTON
+ )
+ )
+ assertTrue(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_GESTURE
+ )
+ )
+ assertFalse(
+ faceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(
+ PowerManager.WAKE_REASON_APPLICATION
+ )
+ )
+ }
+
+ private fun createFaceWakeUpTriggersConfig(wakeUpTriggers: IntArray): FaceWakeUpTriggersConfig {
+ overrideResource(
+ com.android.systemui.R.array.config_face_auth_wake_up_triggers,
+ wakeUpTriggers
+ )
+
+ return FaceWakeUpTriggersConfig(mContext.getResources(), globalSettings, dumpManager)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 627d738a895f..61c7bb500e6a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -44,7 +44,6 @@ 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.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
@@ -105,8 +104,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private FrameLayout mLargeClockFrame;
@Mock
private SecureSettings mSecureSettings;
- @Mock
- private FeatureFlags mFeatureFlags;
private final View mFakeSmartspaceView = new View(mContext);
@@ -143,8 +140,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mSecureSettings,
mExecutor,
mDumpManager,
- mClockEventController,
- mFeatureFlags
+ mClockEventController
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index b885d546c517..f9bec65ab677 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -486,7 +486,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
registeredSwipeListener.onSwipeUp();
- verify(mKeyguardUpdateMonitor).requestFaceAuth(true,
+ verify(mKeyguardUpdateMonitor).requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
}
@@ -499,16 +499,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
registeredSwipeListener.onSwipeUp();
verify(mKeyguardUpdateMonitor, never())
- .requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+ .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
}
@Test
public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(true);
+ when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+ .thenReturn(true);
setupGetSecurityView();
registeredSwipeListener.onSwipeUp();
@@ -520,8 +519,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
getRegisteredSwipeListener();
- when(mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(false);
+ when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+ .thenReturn(false);
setupGetSecurityView();
registeredSwipeListener.onSwipeUp();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c6233b54c028..680c3b8f546b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -110,6 +110,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.telephony.TelephonyListenerManager;
+import com.android.systemui.util.settings.GlobalSettings;
import org.junit.After;
import org.junit.Assert;
@@ -210,6 +211,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private UiEventLogger mUiEventLogger;
@Mock
private PowerManager mPowerManager;
+ @Mock
+ private GlobalSettings mGlobalSettings;
+ private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -237,8 +241,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mActivityService.getCurrentUser()).thenReturn(mCurrentUserInfo);
when(mActivityService.getCurrentUserId()).thenReturn(mCurrentUserId);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
- when(mFaceManager.hasEnrolledTemplates()).thenReturn(true);
- when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
@@ -294,6 +297,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
.when(ActivityManager::getCurrentUser);
ExtendedMockito.doReturn(mActivityService).when(ActivityManager::getService);
+ mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
+ mContext.getResources(),
+ mGlobalSettings,
+ mDumpManager
+ );
+
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
@@ -593,7 +602,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
verify(mFaceManager).isHardwareDetected();
- verify(mFaceManager).hasEnrolledTemplates(anyInt());
+ verify(mFaceManager, never()).hasEnrolledTemplates(anyInt());
}
@Test
@@ -607,16 +616,22 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testTriesToAuthenticate_whenKeyguard() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
- mTestableLooper.processAllMessages();
keyguardIsVisible();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ verify(mUiEventLogger).logWithInstanceIdAndPosition(
+ eq(FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAKING_UP),
+ eq(0),
+ eq(null),
+ any(),
+ eq(PowerManager.WAKE_REASON_POWER_BUTTON));
}
@Test
public void skipsAuthentication_whenStatusBarShadeLocked() {
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -630,7 +645,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
STRONG_AUTH_REQUIRED_AFTER_BOOT);
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -661,7 +676,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
bouncerFullyVisibleAndNotGoingToSleep();
mTestableLooper.processAllMessages();
- boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
NOTIFICATION_PANEL_CLICKED);
assertThat(didFaceAuthRun).isTrue();
@@ -673,7 +688,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
biometricsDisabledForCurrentUser();
mTestableLooper.processAllMessages();
- boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+ boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
NOTIFICATION_PANEL_CLICKED);
assertThat(didFaceAuthRun).isFalse();
@@ -684,7 +699,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
@@ -692,8 +707,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// Stop scanning when bouncer becomes visible
setKeyguardBouncerVisibility(true);
clearInvocations(mFaceManager);
- mKeyguardUpdateMonitor.requestFaceAuth(true,
- FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
}
@@ -709,7 +723,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testTriesToAuthenticate_whenTrustOnAgentKeyguard_ifBypass() {
mKeyguardUpdateMonitor.setKeyguardBypassController(mKeyguardBypassController);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mKeyguardBypassController.canBypass()).thenReturn(true);
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
@@ -721,7 +735,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testIgnoresAuth_whenTrustAgentOnKeyguard_withoutBypass() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>());
@@ -732,7 +746,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testIgnoresAuth_whenLockdown() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
@@ -744,7 +758,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testTriesToAuthenticate_whenLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(
KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
@@ -768,7 +782,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testFaceAndFingerprintLockout_onlyFace() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -779,7 +793,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testFaceAndFingerprintLockout_onlyFingerprint() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -791,7 +805,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testFaceAndFingerprintLockout() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -890,7 +904,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mFaceManager.getLockoutModeForUser(eq(FACE_SENSOR_ID), eq(newUser)))
.thenReturn(faceLockoutMode);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -1064,7 +1078,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testOccludingAppFingerprintListeningState() {
// GIVEN keyguard isn't visible (app occluding)
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
@@ -1079,7 +1093,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testOccludingAppRequestsFingerprint() {
// GIVEN keyguard isn't visible (app occluding)
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
// WHEN an occluding app requests fp
@@ -1170,7 +1184,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
biometricsNotDisabledThroughDevicePolicyManager();
mStatusBarStateListener.onStateChanged(StatusBarState.SHADE_LOCKED);
setKeyguardBouncerVisibility(false /* isVisible */);
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
when(mKeyguardBypassController.canBypass()).thenReturn(true);
keyguardIsVisible();
@@ -1549,7 +1563,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testFingerprintCanAuth_whenCancellationNotReceivedAndAuthFailed() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
keyguardIsVisible();
@@ -1598,6 +1612,36 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
}
+ @Test
+ public void testDreamingStopped_faceDoesNotRun() {
+ mKeyguardUpdateMonitor.dispatchDreamingStopped();
+ mTestableLooper.processAllMessages();
+
+ verify(mFaceManager, never()).authenticate(
+ any(), any(), any(), any(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testFaceWakeupTrigger_runFaceAuth_onlyOnConfiguredTriggers() {
+ // keyguard is visible
+ keyguardIsVisible();
+
+ // WHEN device wakes up from an application
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_APPLICATION);
+ mTestableLooper.processAllMessages();
+
+ // THEN face auth isn't triggered
+ verify(mFaceManager, never()).authenticate(
+ any(), any(), any(), any(), anyInt(), anyBoolean());
+
+ // WHEN device wakes up from the power button
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+
+ // THEN face auth is triggered
+ verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1650,7 +1694,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
private void triggerSuccessfulFaceAuth() {
- mKeyguardUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+ mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager).authenticate(any(),
any(),
mAuthenticationCallbackCaptor.capture(),
@@ -1718,7 +1762,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
private void deviceIsInteractive() {
- mKeyguardUpdateMonitor.dispatchStartedWakingUp();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
}
private void bouncerFullyVisible() {
@@ -1768,7 +1812,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mKeyguardUpdateMonitorLogger, mUiEventLogger, () -> mSessionTracker,
mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
- mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager);
+ mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
+ mFaceWakeUpTriggersConfig);
setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
new file mode 100644
index 000000000000..ae8f419d4e64
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -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.keyguard;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedStateListDrawable;
+import android.util.Pair;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.doze.util.BurnInHelperKt;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+public class LockIconViewControllerBaseTest extends SysuiTestCase {
+ protected static final String UNLOCKED_LABEL = "unlocked";
+ protected static final int PADDING = 10;
+
+ protected MockitoSession mStaticMockSession;
+
+ protected @Mock LockIconView mLockIconView;
+ protected @Mock AnimatedStateListDrawable mIconDrawable;
+ protected @Mock Context mContext;
+ protected @Mock Resources mResources;
+ protected @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager;
+ protected @Mock StatusBarStateController mStatusBarStateController;
+ protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ protected @Mock KeyguardViewController mKeyguardViewController;
+ protected @Mock KeyguardStateController mKeyguardStateController;
+ protected @Mock FalsingManager mFalsingManager;
+ protected @Mock AuthController mAuthController;
+ protected @Mock DumpManager mDumpManager;
+ protected @Mock AccessibilityManager mAccessibilityManager;
+ protected @Mock ConfigurationController mConfigurationController;
+ protected @Mock VibratorHelper mVibrator;
+ protected @Mock AuthRippleController mAuthRippleController;
+ protected @Mock FeatureFlags mFeatureFlags;
+ protected @Mock KeyguardTransitionRepository mTransitionRepository;
+ protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
+
+ protected LockIconViewController mUnderTest;
+
+ // Capture listeners so that they can be used to send events
+ @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+
+ @Captor protected ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
+ ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
+ protected KeyguardStateController.Callback mKeyguardStateCallback;
+
+ @Captor protected ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
+ ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+ protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+ @Captor protected ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+ protected AuthController.Callback mAuthControllerCallback;
+
+ @Captor protected ArgumentCaptor<KeyguardUpdateMonitorCallback>
+ mKeyguardUpdateMonitorCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
+ protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+
+ @Captor protected ArgumentCaptor<Point> mPointCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ mStaticMockSession = mockitoSession()
+ .mockStatic(BurnInHelperKt.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+ MockitoAnnotations.initMocks(this);
+
+ setupLockIconViewMocks();
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+ Rect windowBounds = new Rect(0, 0, 800, 1200);
+ when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
+ when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
+ when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
+ when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
+ when(mAuthController.getScaleFactor()).thenReturn(1f);
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+
+ mUnderTest = new LockIconViewController(
+ mLockIconView,
+ mStatusBarStateController,
+ mKeyguardUpdateMonitor,
+ mKeyguardViewController,
+ mKeyguardStateController,
+ mFalsingManager,
+ mAuthController,
+ mDumpManager,
+ mAccessibilityManager,
+ mConfigurationController,
+ mDelayableExecutor,
+ mVibrator,
+ mAuthRippleController,
+ mResources,
+ new KeyguardTransitionInteractor(mTransitionRepository),
+ new KeyguardInteractor(new FakeKeyguardRepository()),
+ mFeatureFlags
+ );
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ protected Pair<Float, Point> setupUdfps() {
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+ final Point udfpsLocation = new Point(50, 75);
+ final float radius = 33f;
+ when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
+ when(mAuthController.getUdfpsRadius()).thenReturn(radius);
+
+ return new Pair(radius, udfpsLocation);
+ }
+
+ protected void setupShowLockIcon() {
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+ when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
+ }
+
+ protected void captureAuthControllerCallback() {
+ verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+ mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+ }
+
+ protected void captureKeyguardStateCallback() {
+ verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
+ mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
+ }
+
+ protected void captureStatusBarStateListener() {
+ verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
+ mStatusBarStateListener = mStatusBarStateCaptor.getValue();
+ }
+
+ protected void captureKeyguardUpdateMonitorCallback() {
+ verify(mKeyguardUpdateMonitor).registerCallback(
+ mKeyguardUpdateMonitorCallbackCaptor.capture());
+ mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
+ }
+
+ protected void setupLockIconViewMocks() {
+ when(mLockIconView.getResources()).thenReturn(mResources);
+ when(mLockIconView.getContext()).thenReturn(mContext);
+ }
+
+ protected void resetLockIconView() {
+ reset(mLockIconView);
+ setupLockIconViewMocks();
+ }
+
+ protected void init(boolean useMigrationFlag) {
+ when(mFeatureFlags.isEnabled(DOZING_MIGRATION_1)).thenReturn(useMigrationFlag);
+ mUnderTest.init();
+
+ verify(mLockIconView, atLeast(1)).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachCaptor.getValue().onViewAttachedToWindow(mLockIconView);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 000000000000..f4c2284de2d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -0,0 +1,269 @@
+/*
+ * 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.keyguard;
+
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
+
+import static com.android.keyguard.LockIconView.ICON_LOCK;
+import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.hardware.biometrics.BiometricSourceType;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.doze.util.BurnInHelperKt;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
+
+ @Test
+ public void testUpdateFingerprintLocationOnInit() {
+ // GIVEN fp sensor location is available pre-attached
+ Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
+
+ // WHEN lock icon view controller is initialized and attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN lock icon view location is updated to the udfps location with UDFPS radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testUpdatePaddingBasedOnResolutionScale() {
+ // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
+ Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
+ when(mAuthController.getScaleFactor()).thenReturn(5f);
+
+ // WHEN lock icon view controller is initialized and attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN lock icon view location is updated with the scaled radius
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING * 5));
+ }
+
+ @Test
+ public void testUpdateLockIconLocationOnAuthenticatorsRegistered() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ init(/* useMigrationFlag= */false);
+ resetLockIconView(); // reset any method call counts for when we verify method calls later
+
+ // GIVEN fp sensor location is available post-attached
+ captureAuthControllerCallback();
+ Pair<Float, Point> udfps = setupUdfps();
+
+ // WHEN all authenticators are registered
+ mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
+ mDelayableExecutor.runAllReady();
+
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testUpdateLockIconLocationOnUdfpsLocationChanged() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ init(/* useMigrationFlag= */false);
+ resetLockIconView(); // reset any method call counts for when we verify method calls later
+
+ // GIVEN fp sensor location is available post-attached
+ captureAuthControllerCallback();
+ Pair<Float, Point> udfps = setupUdfps();
+
+ // WHEN udfps location changes
+ mAuthControllerCallback.onUdfpsLocationChanged();
+ mDelayableExecutor.runAllReady();
+
+ // THEN lock icon view location is updated with the same coordinates as auth controller vals
+ verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
+ eq(PADDING));
+ }
+
+ @Test
+ public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
+ // GIVEN Udpfs sensor location is available
+ setupUdfps();
+
+ // WHEN the view is attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon view background should be enabled
+ verify(mLockIconView).setUseBackground(true);
+ }
+
+ @Test
+ public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
+ // GIVEN Udfps sensor location is not supported
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
+
+ // WHEN the view is attached
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon view background should be disabled
+ verify(mLockIconView).setUseBackground(false);
+ }
+
+ @Test
+ public void testUnlockIconShows_biometricUnlockedTrue() {
+ // GIVEN UDFPS sensor location is available
+ setupUdfps();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardUpdateMonitorCallback();
+
+ // GIVEN user has unlocked with a biometric auth (ie: face auth)
+ when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
+ reset(mLockIconView);
+
+ // WHEN face auth's biometric running state changes
+ mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
+ BiometricSourceType.FACE);
+
+ // THEN the unlock icon is shown
+ verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
+ }
+
+ @Test
+ public void testLockIconStartState() {
+ // GIVEN lock icon state
+ setupShowLockIcon();
+
+ // WHEN lock icon controller is initialized
+ init(/* useMigrationFlag= */false);
+
+ // THEN the lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_updateToUnlock() {
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureKeyguardStateCallback();
+ reset(mLockIconView);
+
+ // WHEN the unlocked state changes to canDismissLockScreen=true
+ when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
+ mKeyguardStateCallback.onUnlockedChanged();
+
+ // THEN the unlock should show
+ verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
+ }
+
+ @Test
+ public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
+ // GIVEN udfps not enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon();
+ }
+
+ @Test
+ public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon();
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN the dozing state changes
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true);
+ }
+
+ @Test
+ public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
+ // GIVEN udfps enrolled
+ setupUdfps();
+ when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
+
+ // GIVEN burn-in offset = 5
+ int burnInOffset = 5;
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
+
+ // GIVEN starting state for the lock icon (keyguard)
+ setupShowLockIcon();
+ init(/* useMigrationFlag= */false);
+ captureStatusBarStateListener();
+ reset(mLockIconView);
+
+ // WHEN dozing updates
+ mStatusBarStateListener.onDozingChanged(true /* isDozing */);
+ mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
+
+ // THEN the view's translation is updated to use the AoD burn-in offsets
+ verify(mLockIconView).setTranslationY(burnInOffset);
+ verify(mLockIconView).setTranslationX(burnInOffset);
+ reset(mLockIconView);
+
+ // WHEN the device is no longer dozing
+ mStatusBarStateListener.onDozingChanged(false /* isDozing */);
+ mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
+
+ // THEN the view is updated to NO translation (no burn-in offsets anymore)
+ verify(mLockIconView).setTranslationY(0);
+ verify(mLockIconView).setTranslationX(0);
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
new file mode 100644
index 000000000000..d2c54b4cc0e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.keyguard
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.keyguard.LockIconView.ICON_LOCK
+import com.android.systemui.doze.util.getBurnInOffset
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LockIconViewControllerWithCoroutinesTest : LockIconViewControllerBaseTest() {
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps not enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false)
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon()
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN the dozing state changes
+ mUnderTest.mIsDozingCallback.accept(true)
+
+ // THEN the icon is cleared
+ verify(mLockIconView).clearIcon()
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testLockIcon_updateToAodLock_whenUdfpsEnrolled() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
+
+ // GIVEN starting state for the lock icon
+ setupShowLockIcon()
+
+ // GIVEN lock icon controller is initialized and view is attached
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN the dozing state changes
+ mUnderTest.mIsDozingCallback.accept(true)
+
+ // THEN the AOD lock icon should show
+ verify(mLockIconView).updateIcon(ICON_LOCK, true)
+ }
+
+ /** After migration, replaces LockIconViewControllerTest version */
+ @Test
+ fun testBurnInOffsetsUpdated_onDozeAmountChanged() =
+ runBlocking(IMMEDIATE) {
+ // GIVEN udfps enrolled
+ setupUdfps()
+ whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true)
+
+ // GIVEN burn-in offset = 5
+ val burnInOffset = 5
+ whenever(getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
+
+ // GIVEN starting state for the lock icon (keyguard)
+ setupShowLockIcon()
+ init(/* useMigrationFlag= */ true)
+ reset(mLockIconView)
+
+ // WHEN dozing updates
+ mUnderTest.mIsDozingCallback.accept(true)
+ mUnderTest.mDozeTransitionCallback.accept(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+
+ // THEN the view's translation is updated to use the AoD burn-in offsets
+ verify(mLockIconView).setTranslationY(burnInOffset.toFloat())
+ verify(mLockIconView).setTranslationX(burnInOffset.toFloat())
+ reset(mLockIconView)
+
+ // WHEN the device is no longer dozing
+ mUnderTest.mIsDozingCallback.accept(false)
+ mUnderTest.mDozeTransitionCallback.accept(TransitionStep(AOD, LOCKSCREEN, 0f, FINISHED))
+
+ // THEN the view is updated to NO translation (no burn-in offsets anymore)
+ verify(mLockIconView).setTranslationY(0f)
+ verify(mLockIconView).setTranslationX(0f)
+ }
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 19a6c66652dd..77d38c58e685 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -35,6 +35,7 @@ import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -68,6 +69,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
public MockitoRule mockito = MockitoJUnit.rule();
private Context mContextWrapper;
+ private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
private AccessibilityButtonTargetsObserver mTargetsObserver;
@@ -87,6 +89,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
}
};
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
@@ -348,8 +351,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
- displayManager, mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor,
- featureFlags);
+ displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
+ mKeyguardUpdateMonitor, featureFlags);
controller.init();
return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
new file mode 100644
index 000000000000..8ef65dcb2c3a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.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.systemui.accessibility.floatingmenu;
+
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.bubbles.DismissView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link DismissAnimationController}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DismissAnimationControllerTest extends SysuiTestCase {
+ private DismissAnimationController mDismissAnimationController;
+ private DismissView mDismissView;
+
+ @Before
+ public void setUp() throws Exception {
+ final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
+ final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
+ stubWindowManager);
+ final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
+ stubMenuViewAppearance);
+ mDismissView = new DismissView(mContext);
+ mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
+ }
+
+ @Test
+ public void showDismissView_success() {
+ mDismissAnimationController.showDismissView(true);
+
+ verify(mDismissView).show();
+ }
+
+ @Test
+ public void hideDismissView_success() {
+ mDismissAnimationController.showDismissView(false);
+
+ verify(mDismissView).hide();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index dbf291c49ee5..d0bd4f7026eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -18,9 +18,16 @@ package com.android.systemui.accessibility.floatingmenu;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
import android.graphics.PointF;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -36,6 +43,8 @@ import org.junit.runner.RunWith;
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class MenuAnimationControllerTest extends SysuiTestCase {
+
+ private ViewPropertyAnimator mViewPropertyAnimator;
private MenuView mMenuView;
private MenuAnimationController mMenuAnimationController;
@@ -45,7 +54,11 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
- mMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mViewPropertyAnimator = spy(mMenuView.animate());
+ doReturn(mViewPropertyAnimator).when(mMenuView).animate();
+
mMenuAnimationController = new MenuAnimationController(mMenuView);
}
@@ -58,4 +71,20 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
assertThat(mMenuView.getTranslationX()).isEqualTo(50);
assertThat(mMenuView.getTranslationY()).isEqualTo(60);
}
+
+ @Test
+ public void startShrinkAnimation_verifyAnimationEndAction() {
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(View.VISIBLE));
+
+ verify(mViewPropertyAnimator).withEndAction(any(Runnable.class));
+ }
+
+ @Test
+ public void startGrowAnimation_menuCompletelyOpaque() {
+ mMenuAnimationController.startShrinkAnimation(null);
+
+ mMenuAnimationController.startGrowAnimation();
+
+ assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index bf6d574a0f67..78ee627a9a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -43,6 +43,7 @@ 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;
@@ -54,6 +55,9 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private DismissAnimationController.DismissCallback mStubDismissCallback;
+
private RecyclerView mStubListView;
private MenuView mMenuView;
private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
@@ -87,7 +91,7 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
- assertThat(info.getActionList().size()).isEqualTo(5);
+ assertThat(info.getActionList().size()).isEqualTo(6);
}
@Test
@@ -156,6 +160,17 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
}
@Test
+ public void performRemoveMenuAction_success() {
+ mMenuAnimationController.setDismissCallback(mStubDismissCallback);
+ final boolean removeMenuAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_remove_menu, null);
+
+ assertThat(removeMenuAction).isTrue();
+ verify(mMenuAnimationController).removeMenu();
+ }
+
+ @Test
public void performFocusAction_fadeIn() {
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
ACTION_ACCESSIBILITY_FOCUS, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index c5b9a294fc34..4acb394bee95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -21,6 +21,8 @@ import static android.view.View.OVER_SCROLL_NEVER;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -36,6 +38,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.wm.shell.bubbles.DismissView;
import org.junit.After;
import org.junit.Before;
@@ -57,7 +60,9 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
private MenuView mStubMenuView;
private MenuListViewTouchHandler mTouchHandler;
private MenuAnimationController mMenuAnimationController;
+ private DismissAnimationController mDismissAnimationController;
private RecyclerView mStubListView;
+ private DismissView mDismissView;
@Before
public void setUp() throws Exception {
@@ -69,7 +74,11 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
mStubMenuView.setTranslationX(0);
mStubMenuView.setTranslationY(0);
mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
- mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController);
+ mDismissView = spy(new DismissView(mContext));
+ mDismissAnimationController =
+ spy(new DismissAnimationController(mDismissView, mStubMenuView));
+ mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
+ mDismissAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
@@ -88,7 +97,9 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
}
@Test
- public void onActionMoveEvent_shouldMoveToPosition() {
+ public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
+ doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
+ any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
@@ -108,6 +119,24 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
}
@Test
+ public void onActionMoveEvent_shouldShowDismissView() {
+ final int offset = 100;
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+ final MotionEvent stubMoveEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+ MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+ verify(mDismissView).show();
+ }
+
+ @Test
public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 8c8d6aca7cd7..dd7ce0e06c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -34,6 +34,7 @@ import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
@@ -59,6 +60,9 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
private WindowManager mWindowManager;
@Mock
+ private AccessibilityManager mAccessibilityManager;
+
+ @Mock
private WindowMetrics mWindowMetrics;
private MenuViewLayerController mMenuViewLayerController;
@@ -72,7 +76,8 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
- mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager);
+ mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager,
+ mAccessibilityManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 23c6ef1338b3..d20eeafde09c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -23,18 +23,25 @@ import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.Laye
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
/** Tests for {@link MenuViewLayer}. */
@RunWith(AndroidTestingRunner.class)
@@ -43,10 +50,19 @@ import org.junit.runner.RunWith;
public class MenuViewLayerTest extends SysuiTestCase {
private MenuViewLayer mMenuViewLayer;
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private IAccessibilityFloatingMenu mFloatingMenu;
+
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager);
+ final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
+ AccessibilityManager.class);
+ mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
+ mFloatingMenu);
}
@Test
@@ -64,4 +80,11 @@ public class MenuViewLayerTest extends SysuiTestCase {
assertThat(menuView.getVisibility()).isEqualTo(GONE);
}
+
+ @Test
+ public void tiggerDismissMenuAction_hideFloatingMenu() {
+ mMenuViewLayer.mDismissMenuAction.run();
+
+ verify(mFloatingMenu).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index d1107c612977..45b8ce1ce247 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -41,10 +41,15 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
import org.junit.After
import org.junit.Rule
import org.junit.Test
@@ -80,6 +85,15 @@ class AuthContainerViewTest : SysuiTestCase() {
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
+ private val biometricPromptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+ private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
+ Dispatchers.Main.immediate,
+ biometricPromptRepository,
+ credentialInteractor
+ )
+ private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+
private var authContainer: TestAuthContainerView? = null
@After
@@ -466,6 +480,8 @@ class AuthContainerViewTest : SysuiTestCase() {
userManager,
lockPatternUtils,
interactionJankMonitor,
+ { bpCredentialInteractor },
+ { credentialViewModel },
Handler(TestableLooper.get(this).looper),
FakeExecutor(FakeSystemClock())
) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 8e45067c8e13..4dd46edd0912 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -25,7 +25,9 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWA
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -87,6 +89,8 @@ import com.android.internal.R;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
+import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -163,6 +167,11 @@ public class AuthControllerTest extends SysuiTestCase {
private UdfpsLogger mUdfpsLogger;
@Mock
private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
+ private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
+ @Mock
+ private CredentialViewModel mCredentialViewModel;
+
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
@Captor
@@ -236,7 +245,7 @@ public class AuthControllerTest extends SysuiTestCase {
2 /* sensorId */,
SensorProperties.STRENGTH_STRONG,
1 /* maxEnrollmentsPerUser */,
- fpComponentInfo,
+ faceComponentInfo,
FaceSensorProperties.TYPE_RGB,
true /* supportsFaceDetection */,
true /* supportsSelfIllumination */,
@@ -276,8 +285,6 @@ public class AuthControllerTest extends SysuiTestCase {
reset(mFingerprintManager);
reset(mFaceManager);
- when(mVibratorHelper.hasVibrator()).thenReturn(true);
-
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
@@ -308,8 +315,6 @@ public class AuthControllerTest extends SysuiTestCase {
reset(mFingerprintManager);
reset(mFaceManager);
- when(mVibratorHelper.hasVibrator()).thenReturn(true);
-
// This test requires an uninitialized AuthController.
AuthController authController = new TestableAuthController(mContextSpy, mExecution,
mCommandQueue, mActivityTaskManager, mWindowManager, mFingerprintManager,
@@ -343,6 +348,36 @@ public class AuthControllerTest extends SysuiTestCase {
}
@Test
+ public void testFaceAuthEnrollmentStatus() throws RemoteException {
+ final int userId = 0;
+
+ reset(mFaceManager);
+ mAuthController.start();
+
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorsRegisteredCaptor.capture());
+
+ mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFaceManager.getSensorPropertiesInternal());
+ mTestableLooper.processAllMessages();
+
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateCaptor.capture());
+
+ assertFalse(mAuthController.isFaceAuthEnrolled(userId));
+
+ // Enrollments changed for an unknown sensor.
+ for (BiometricStateListener listener : mBiometricStateCaptor.getAllValues()) {
+ listener.onEnrollmentsChanged(userId,
+ 2 /* sensorId */, true /* hasEnrollments */);
+ }
+ mTestableLooper.processAllMessages();
+
+ assertTrue(mAuthController.isFaceAuthEnrolled(userId));
+ }
+
+
+ @Test
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
@@ -981,6 +1016,7 @@ public class AuthControllerTest extends SysuiTestCase {
fingerprintManager, faceManager, udfpsControllerFactory,
sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle,
mUserManager, mLockPatternUtils, mUdfpsLogger, statusBarStateController,
+ () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel,
mInteractionJankMonitor, mHandler, mBackgroundExecutor, vibratorHelper);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 8820c164cba4..1379a0eeebdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -22,12 +22,11 @@ import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.SensorProperties
-import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.face.FaceSensorProperties
+import android.hardware.face.FaceSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.os.Bundle
-
import android.testing.ViewUtils
import android.view.LayoutInflater
@@ -83,26 +82,31 @@ internal fun AuthBiometricView?.destroyDialog() {
internal fun fingerprintSensorPropertiesInternal(
ids: List<Int> = listOf(0)
): List<FingerprintSensorPropertiesInternal> {
- val componentInfo = listOf(
+ val componentInfo =
+ listOf(
ComponentInfoInternal(
- "fingerprintSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */
+ "fingerprintSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
),
ComponentInfoInternal(
- "matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */,
+ "" /* firmwareVersion */,
+ "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
)
- )
+ )
return ids.map { id ->
FingerprintSensorPropertiesInternal(
- id,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
- false /* resetLockoutRequiresHardwareAuthToken */
+ id,
+ SensorProperties.STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FingerprintSensorProperties.TYPE_REAR,
+ false /* resetLockoutRequiresHardwareAuthToken */
)
}
}
@@ -111,28 +115,53 @@ internal fun fingerprintSensorPropertiesInternal(
internal fun faceSensorPropertiesInternal(
ids: List<Int> = listOf(1)
): List<FaceSensorPropertiesInternal> {
- val componentInfo = listOf(
+ val componentInfo =
+ listOf(
ComponentInfoInternal(
- "faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */
+ "faceSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */,
+ "" /* softwareVersion */
),
ComponentInfoInternal(
- "matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */
+ "matchingAlgorithm" /* componentId */,
+ "" /* hardwareVersion */,
+ "" /* firmwareVersion */,
+ "" /* serialNumber */,
+ "vendor/version/revision" /* softwareVersion */
)
- )
+ )
return ids.map { id ->
FaceSensorPropertiesInternal(
- id,
- SensorProperties.STRENGTH_STRONG,
- 2 /* maxEnrollmentsPerUser */,
- componentInfo,
- FaceSensorProperties.TYPE_RGB,
- true /* supportsFaceDetection */,
- true /* supportsSelfIllumination */,
- false /* resetLockoutRequiresHardwareAuthToken */
+ id,
+ SensorProperties.STRENGTH_STRONG,
+ 2 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ FaceSensorProperties.TYPE_RGB,
+ true /* supportsFaceDetection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */
)
}
}
+
+internal fun promptInfo(
+ title: String = "title",
+ subtitle: String = "sub",
+ description: String = "desc",
+ credentialTitle: String? = "cred title",
+ credentialSubtitle: String? = "cred sub",
+ credentialDescription: String? = "cred desc",
+ negativeButton: String = "neg",
+): PromptInfo {
+ val info = PromptInfo()
+ info.title = title
+ info.subtitle = subtitle
+ info.description = description
+ credentialTitle?.let { info.deviceCredentialTitle = it }
+ credentialSubtitle?.let { info.deviceCredentialSubtitle = it }
+ credentialDescription?.let { info.deviceCredentialDescription = it }
+ info.negativeButtonText = negativeButton
+ return info
+}
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 49c6fd14997e..ed957db2852b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -69,6 +69,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -169,6 +170,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
private FakeExecutor mFgExecutor;
@Mock
private UdfpsDisplayMode mUdfpsDisplayMode;
+ @Mock
+ private FeatureFlags mFeatureFlags;
// Stuff for configuring mocks
@Mock
@@ -250,6 +253,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
+ mFeatureFlags,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
new file mode 100644
index 000000000000..2d5614c15173
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -0,0 +1,81 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptRepositoryImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var authController: AuthController
+
+ private lateinit var repository: PromptRepositoryImpl
+
+ @Before
+ fun setup() {
+ repository = PromptRepositoryImpl(authController)
+ }
+
+ @Test
+ fun isShowing() = runBlockingTest {
+ whenever(authController.isShowing).thenReturn(true)
+
+ val values = mutableListOf<Boolean>()
+ val job = launch { repository.isShowing.toList(values) }
+ assertThat(values).containsExactly(true)
+
+ withArgCaptor<AuthController.Callback> {
+ verify(authController).addCallback(capture())
+
+ value.onBiometricPromptShown()
+ assertThat(values).containsExactly(true, true)
+
+ value.onBiometricPromptDismissed()
+ assertThat(values).containsExactly(true, true, false).inOrder()
+
+ job.cancel()
+ verify(authController).removeCallback(eq(value))
+ }
+ }
+
+ @Test
+ fun setsAndUnsetsPrompt() = runBlockingTest {
+ val kind = PromptKind.PIN
+ val uid = 8
+ val challenge = 90L
+ val promptInfo = PromptInfo()
+
+ repository.setPrompt(promptInfo, uid, challenge, kind)
+
+ assertThat(repository.kind.value).isEqualTo(kind)
+ assertThat(repository.userId.value).isEqualTo(uid)
+ assertThat(repository.challenge.value).isEqualTo(challenge)
+ assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+
+ repository.unsetPrompt()
+
+ assertThat(repository.promptInfo.value).isNull()
+ assertThat(repository.userId.value).isNull()
+ assertThat(repository.challenge.value).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
new file mode 100644
index 000000000000..97d3e688ed80
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -0,0 +1,216 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResourcesManager
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockscreenCredential
+import com.android.internal.widget.VerifyCredentialResponse
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 22
+private const val OPERATION_ID = 100L
+private const val MAX_ATTEMPTS = 5
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class CredentialInteractorImplTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var devicePolicyResourcesManager: DevicePolicyResourcesManager
+
+ private val systemClock = FakeSystemClock()
+
+ private lateinit var interactor: CredentialInteractorImpl
+
+ @Before
+ fun setup() {
+ whenever(devicePolicyManager.resources).thenReturn(devicePolicyResourcesManager)
+ whenever(lockPatternUtils.getMaximumFailedPasswordsForWipe(anyInt()))
+ .thenReturn(MAX_ATTEMPTS)
+ whenever(userManager.getUserInfo(eq(USER_ID))).thenReturn(UserInfo(USER_ID, "", 0))
+ whenever(devicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(eq(USER_ID)))
+ .thenReturn(USER_ID)
+
+ interactor =
+ CredentialInteractorImpl(
+ mContext,
+ lockPatternUtils,
+ userManager,
+ devicePolicyManager,
+ systemClock
+ )
+ }
+
+ @Test
+ fun testStealthMode() {
+ for (value in listOf(true, false, false, true)) {
+ whenever(lockPatternUtils.isVisiblePatternEnabled(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.isStealthModeActive(USER_ID)).isEqualTo(!value)
+ }
+ }
+
+ @Test
+ fun testCredentialOwner() {
+ for (value in listOf(12, 8, 4)) {
+ whenever(userManager.getCredentialOwnerProfile(eq(USER_ID))).thenReturn(value)
+
+ assertThat(interactor.getCredentialOwnerOrSelfId(USER_ID)).isEqualTo(value)
+ }
+ }
+
+ @Test fun pinCredentialWhenGood() = pinCredential(goodCredential())
+
+ @Test fun pinCredentialWhenBad() = pinCredential(badCredential())
+
+ @Test fun pinCredentialWhenBadAndThrottled() = pinCredential(badCredential(timeout = 5_000))
+
+ private fun pinCredential(result: VerifyCredentialResponse) = runTest {
+ val usedAttempts = 1
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(usedAttempts)
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt())).thenReturn(result)
+ whenever(lockPatternUtils.verifyGatekeeperPasswordHandle(anyLong(), anyLong(), eq(USER_ID)))
+ .thenReturn(result)
+ whenever(lockPatternUtils.setLockoutAttemptDeadline(anyInt(), anyInt())).thenAnswer {
+ systemClock.elapsedRealtime() + (it.arguments[1] as Int)
+ }
+
+ // wrap in an async block so the test can advance the clock if throttling credential
+ // checks prevents the method from returning
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val last = statusList.removeLastOrNull()
+ if (result.isMatched) {
+ assertThat(statusList).isEmpty()
+ val successfulResult = last as? CredentialStatus.Success.Verified
+ assertThat(successfulResult).isNotNull()
+ assertThat(successfulResult!!.hat).isEqualTo(result.gatekeeperHAT)
+
+ verify(lockPatternUtils).userPresent(eq(USER_ID))
+ verify(lockPatternUtils)
+ .removeGatekeeperPasswordHandle(eq(result.gatekeeperPasswordHandle))
+ } else {
+ val failedResult = last as? CredentialStatus.Fail.Error
+ assertThat(failedResult).isNotNull()
+ assertThat(failedResult!!.remainingAttempts)
+ .isEqualTo(if (result.timeout > 0) null else MAX_ATTEMPTS - usedAttempts - 1)
+ assertThat(failedResult.urgentMessage).isNull()
+
+ if (result.timeout > 0) { // failed and throttled
+ // messages are in the throttled errors, so the final Error.error is empty
+ assertThat(failedResult.error).isEmpty()
+ assertThat(statusList).isNotEmpty()
+ assertThat(statusList.filterIsInstance(CredentialStatus.Fail.Throttled::class.java))
+ .hasSize(statusList.size)
+
+ verify(lockPatternUtils).setLockoutAttemptDeadline(eq(USER_ID), eq(result.timeout))
+ } else { // failed
+ assertThat(failedResult.error)
+ .matches(Regex("(.*)try again(.*)", RegexOption.IGNORE_CASE).toPattern())
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+ }
+ }
+
+ @Test
+ fun pinCredentialWhenBadAndFinalAttempt() = runTest {
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ .thenReturn(badCredential())
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(MAX_ATTEMPTS - 2)
+
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val result = statusList.removeLastOrNull() as? CredentialStatus.Fail.Error
+ assertThat(result).isNotNull()
+ assertThat(result!!.remainingAttempts).isEqualTo(1)
+ assertThat(result.urgentMessage).isNotEmpty()
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+
+ @Test
+ fun pinCredentialWhenBadAndNoMoreAttempts() = runTest {
+ whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ .thenReturn(badCredential())
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ .thenReturn(MAX_ATTEMPTS - 1)
+ whenever(devicePolicyResourcesManager.getString(any(), any())).thenReturn("wipe")
+
+ val statusList = mutableListOf<CredentialStatus>()
+ interactor
+ .verifyCredential(pinRequest(), LockscreenCredential.createPin("1234"))
+ .toList(statusList)
+
+ val result = statusList.removeLastOrNull() as? CredentialStatus.Fail.Error
+ assertThat(result).isNotNull()
+ assertThat(result!!.remainingAttempts).isEqualTo(0)
+ assertThat(result.urgentMessage).isNotEmpty()
+ assertThat(statusList).isEmpty()
+
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ }
+}
+
+private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+private fun goodCredential(
+ passwordHandle: Long = 90,
+ hat: ByteArray = ByteArray(69),
+): VerifyCredentialResponse =
+ VerifyCredentialResponse.Builder()
+ .setGatekeeperPasswordHandle(passwordHandle)
+ .setGatekeeperHAT(hat)
+ .build()
+
+private fun badCredential(timeout: Int = 0): VerifyCredentialResponse =
+ if (timeout > 0) {
+ VerifyCredentialResponse.fromTimeout(timeout)
+ } else {
+ VerifyCredentialResponse.fromError()
+ }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
new file mode 100644
index 000000000000..dbcbf415221e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -0,0 +1,270 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import android.hardware.biometrics.PromptInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.domain.model.BiometricUserInfo
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.junit.MockitoJUnit
+
+private const val USER_ID = 22
+private const val OPERATION_ID = 100L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class PromptCredentialInteractorTest : SysuiTestCase() {
+
+ @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val biometricPromptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+
+ private lateinit var interactor: BiometricPromptCredentialInteractor
+
+ @Before
+ fun setup() {
+ interactor =
+ BiometricPromptCredentialInteractor(
+ dispatcher,
+ biometricPromptRepository,
+ credentialInteractor
+ )
+ }
+
+ @Test
+ fun testIsShowing() =
+ runTest(dispatcher) {
+ var showing = false
+ val job = launch { interactor.isShowing.collect { showing = it } }
+
+ biometricPromptRepository.setIsShowing(false)
+ assertThat(showing).isFalse()
+
+ biometricPromptRepository.setIsShowing(true)
+ assertThat(showing).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun testShowError() =
+ runTest(dispatcher) {
+ var error: CredentialStatus.Fail? = null
+ val job = launch { interactor.verificationError.collect { error = it } }
+
+ for (msg in listOf("once", "again")) {
+ interactor.setVerificationError(error(msg))
+ assertThat(error).isEqualTo(error(msg))
+ }
+
+ interactor.resetVerificationError()
+ assertThat(error).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun nullWhenNoPromptInfo() =
+ runTest(dispatcher) {
+ var prompt: BiometricPromptRequest? = null
+ val job = launch { interactor.prompt.collect { prompt = it } }
+
+ assertThat(prompt).isNull()
+
+ job.cancel()
+ }
+
+ @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN)
+
+ @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD)
+
+ @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN)
+
+ private fun useCredentialForPrompt(kind: Int) =
+ runTest(dispatcher) {
+ val isStealth = false
+ credentialInteractor.stealthMode = isStealth
+
+ var prompt: BiometricPromptRequest? = null
+ val job = launch { interactor.prompt.collect { prompt = it } }
+
+ val title = "what a prompt"
+ val subtitle = "s"
+ val description = "something to see"
+
+ interactor.useCredentialsForAuthentication(
+ PromptInfo().also {
+ it.title = title
+ it.description = description
+ it.subtitle = subtitle
+ },
+ kind = kind,
+ userId = USER_ID,
+ challenge = OPERATION_ID
+ )
+
+ val p = prompt as? BiometricPromptRequest.Credential
+ assertThat(p).isNotNull()
+ assertThat(p!!.title).isEqualTo(title)
+ assertThat(p.subtitle).isEqualTo(subtitle)
+ assertThat(p.description).isEqualTo(description)
+ assertThat(p.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(p.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ assertThat(p)
+ .isInstanceOf(
+ when (kind) {
+ Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java
+ Utils.CREDENTIAL_PASSWORD ->
+ BiometricPromptRequest.Credential.Password::class.java
+ Utils.CREDENTIAL_PATTERN ->
+ BiometricPromptRequest.Credential.Pattern::class.java
+ else -> throw Exception("wrong kind")
+ }
+ )
+ if (p is BiometricPromptRequest.Credential.Pattern) {
+ assertThat(p.stealthMode).isEqualTo(isStealth)
+ }
+
+ interactor.resetPrompt()
+
+ assertThat(prompt).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredential() =
+ runTest(dispatcher) {
+ val hat = ByteArray(4)
+ credentialInteractor.verifyCredentialResponse = { _ -> flowOf(verified(hat)) }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Success.Verified
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.hat).isSameInstanceAs(hat)
+ assertThat(errors.map { it?.error }).containsExactly(null)
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBad() =
+ runTest(dispatcher) {
+ val errorMessage = "bad"
+ val remainingAttempts = 12
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(error(errorMessage, remainingAttempts))
+ }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
+ assertThat(checked.urgentMessage).isNull()
+ assertThat(errors.map { it?.error }).containsExactly(null, errorMessage).inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBadAndUrgentMessage() =
+ runTest(dispatcher) {
+ val error = "not so bad"
+ val urgentMessage = "really bad"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(error(error, 10, urgentMessage))
+ }
+
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.urgentMessage).isEqualTo(urgentMessage)
+ assertThat(errors.map { it?.error }).containsExactly(null, error).inOrder()
+ assertThat(errors.last() as? CredentialStatus.Fail.Error)
+ .isEqualTo(error(error, 10, urgentMessage))
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBadAndThrottled() =
+ runTest(dispatcher) {
+ val remainingAttempts = 3
+ val error = ":("
+ val urgentMessage = ":D"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flow {
+ for (i in 1..3) {
+ emit(throttled("$i"))
+ delay(100)
+ }
+ emit(error(error, remainingAttempts, urgentMessage))
+ }
+ }
+ val errors = mutableListOf<CredentialStatus.Fail?>()
+ val job = launch { interactor.verificationError.toList(errors) }
+
+ val checked =
+ interactor.checkCredential(pinRequest(), text = "1234")
+ as? CredentialStatus.Fail.Error
+
+ assertThat(checked).isNotNull()
+ assertThat(checked!!.remainingAttempts).isEqualTo(remainingAttempts)
+ assertThat(checked.urgentMessage).isEqualTo(urgentMessage)
+ assertThat(errors.map { it?.error })
+ .containsExactly(null, "1", "2", "3", error)
+ .inOrder()
+
+ job.cancel()
+ }
+}
+
+private fun pinRequest(): BiometricPromptRequest.Credential.Pin =
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+private fun verified(hat: ByteArray) = CredentialStatus.Success.Verified(hat)
+
+private fun throttled(error: String) = CredentialStatus.Fail.Throttled(error)
+
+private fun error(error: String? = null, remaining: Int? = null, urgentMessage: String? = null) =
+ CredentialStatus.Fail.Error(error, remaining, urgentMessage)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
new file mode 100644
index 000000000000..4c5e3c1bc6a6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -0,0 +1,93 @@
+package com.android.systemui.biometrics.domain.model
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val USER_ID = 2
+private const val OPERATION_ID = 8L
+
+@SmallTest
+@RunWith(JUnit4::class)
+class BiometricPromptRequestTest : SysuiTestCase() {
+
+ @Test
+ fun biometricRequestFromPromptInfo() {
+ val title = "what"
+ val subtitle = "a"
+ val description = "request"
+
+ val request =
+ BiometricPromptRequest.Biometric(
+ promptInfo(title = title, subtitle = subtitle, description = description),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ )
+
+ assertThat(request.title).isEqualTo(title)
+ assertThat(request.subtitle).isEqualTo(subtitle)
+ assertThat(request.description).isEqualTo(description)
+ assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ }
+
+ @Test
+ fun credentialRequestFromPromptInfo() {
+ val title = "what"
+ val subtitle = "a"
+ val description = "request"
+ val stealth = true
+
+ val toCheck =
+ listOf(
+ BiometricPromptRequest.Credential.Pin(
+ promptInfo(
+ title = title,
+ subtitle = subtitle,
+ description = description,
+ credentialTitle = null,
+ credentialSubtitle = null,
+ credentialDescription = null
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ ),
+ BiometricPromptRequest.Credential.Password(
+ promptInfo(
+ credentialTitle = title,
+ credentialSubtitle = subtitle,
+ credentialDescription = description
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID)
+ ),
+ BiometricPromptRequest.Credential.Pattern(
+ promptInfo(
+ subtitle = subtitle,
+ description = description,
+ credentialTitle = title,
+ credentialSubtitle = null,
+ credentialDescription = null
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID),
+ stealth
+ )
+ )
+
+ for (request in toCheck) {
+ assertThat(request.title).isEqualTo(title)
+ assertThat(request.subtitle).isEqualTo(subtitle)
+ assertThat(request.description).isEqualTo(description)
+ assertThat(request.userInfo).isEqualTo(BiometricUserInfo(USER_ID))
+ assertThat(request.operationInfo).isEqualTo(BiometricOperationInfo(OPERATION_ID))
+ if (request is BiometricPromptRequest.Credential.Pattern) {
+ assertThat(request.stealthMode).isEqualTo(stealth)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
new file mode 100644
index 000000000000..d73cdfc4249f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt
@@ -0,0 +1,181 @@
+package com.android.systemui.biometrics.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.model.PromptKind
+import com.android.systemui.biometrics.data.repository.FakePromptRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
+import com.android.systemui.biometrics.domain.interactor.CredentialStatus
+import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
+import com.android.systemui.biometrics.promptInfo
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val USER_ID = 9
+private const val OPERATION_ID = 10L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class CredentialViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = UnconfinedTestDispatcher()
+ private val promptRepository = FakePromptRepository()
+ private val credentialInteractor = FakeCredentialInteractor()
+
+ private lateinit var viewModel: CredentialViewModel
+
+ @Before
+ fun setup() {
+ viewModel =
+ CredentialViewModel(
+ mContext,
+ BiometricPromptCredentialInteractor(
+ dispatcher,
+ promptRepository,
+ credentialInteractor
+ )
+ )
+ }
+
+ @Test fun setsPinInputFlags() = setsInputFlags(PromptKind.PIN, expectFlags = true)
+ @Test fun setsPasswordInputFlags() = setsInputFlags(PromptKind.PASSWORD, expectFlags = false)
+ @Test fun setsPatternInputFlags() = setsInputFlags(PromptKind.PATTERN, expectFlags = false)
+
+ private fun setsInputFlags(type: PromptKind, expectFlags: Boolean) =
+ runTestWithKind(type) {
+ var flags: Int? = null
+ val job = launch { viewModel.inputFlags.collect { flags = it } }
+
+ if (expectFlags) {
+ assertThat(flags).isNotNull()
+ } else {
+ assertThat(flags).isNull()
+ }
+ job.cancel()
+ }
+
+ @Test fun isStealthIgnoredByPin() = isStealthMode(PromptKind.PIN, expectStealth = false)
+ @Test
+ fun isStealthIgnoredByPassword() = isStealthMode(PromptKind.PASSWORD, expectStealth = false)
+ @Test fun isStealthUsedByPattern() = isStealthMode(PromptKind.PATTERN, expectStealth = true)
+
+ private fun isStealthMode(type: PromptKind, expectStealth: Boolean) =
+ runTestWithKind(type, init = { credentialInteractor.stealthMode = true }) {
+ var stealth: Boolean? = null
+ val job = launch { viewModel.stealthMode.collect { stealth = it } }
+
+ assertThat(stealth).isEqualTo(expectStealth)
+
+ job.cancel()
+ }
+
+ @Test
+ fun animatesContents() = runTestWithKind {
+ val expected = arrayOf(true, false, true)
+ val animate = mutableListOf<Boolean>()
+ val job = launch { viewModel.animateContents.toList(animate) }
+
+ for (value in expected) {
+ viewModel.setAnimateContents(value)
+ viewModel.setAnimateContents(value)
+ }
+ assertThat(animate).containsExactly(*expected).inOrder()
+
+ job.cancel()
+ }
+
+ @Test
+ fun showAndClearErrors() = runTestWithKind {
+ var error = ""
+ val job = launch { viewModel.errorMessage.collect { error = it } }
+ assertThat(error).isEmpty()
+
+ viewModel.showPatternTooShortError()
+ assertThat(error).isNotEmpty()
+
+ viewModel.resetErrorMessage()
+ assertThat(error).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredential() = runTestWithKind {
+ val hat = ByteArray(2)
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(CredentialStatus.Success.Verified(hat))
+ }
+
+ val attestations = mutableListOf<ByteArray?>()
+ val remainingAttempts = mutableListOf<RemainingAttempts?>()
+ var header: HeaderViewModel? = null
+ val job = launch {
+ launch { viewModel.validatedAttestation.toList(attestations) }
+ launch { viewModel.remainingAttempts.toList(remainingAttempts) }
+ launch { viewModel.header.collect { header = it } }
+ }
+ assertThat(header).isNotNull()
+
+ viewModel.checkCredential("p", header!!)
+
+ val attestation = attestations.removeLastOrNull()
+ assertThat(attestation).isSameInstanceAs(hat)
+ assertThat(attestations).isEmpty()
+ assertThat(remainingAttempts).containsExactly(RemainingAttempts())
+
+ job.cancel()
+ }
+
+ @Test
+ fun checkCredentialWhenBad() = runTestWithKind {
+ val remaining = 2
+ val urgentError = "wow"
+ credentialInteractor.verifyCredentialResponse = { _ ->
+ flowOf(CredentialStatus.Fail.Error("error", remaining, urgentError))
+ }
+
+ val attestations = mutableListOf<ByteArray?>()
+ val remainingAttempts = mutableListOf<RemainingAttempts?>()
+ var header: HeaderViewModel? = null
+ val job = launch {
+ launch { viewModel.validatedAttestation.toList(attestations) }
+ launch { viewModel.remainingAttempts.toList(remainingAttempts) }
+ launch { viewModel.header.collect { header = it } }
+ }
+ assertThat(header).isNotNull()
+
+ viewModel.checkCredential("1111", header!!)
+
+ assertThat(attestations).containsExactly(null)
+
+ val attemptInfo = remainingAttempts.removeLastOrNull()
+ assertThat(attemptInfo).isNotNull()
+ assertThat(attemptInfo!!.remaining).isEqualTo(remaining)
+ assertThat(attemptInfo.message).isEqualTo(urgentError)
+ assertThat(remainingAttempts).containsExactly(RemainingAttempts()) // initial value
+
+ job.cancel()
+ }
+
+ private fun runTestWithKind(
+ kind: PromptKind = PromptKind.PIN,
+ init: () -> Unit = {},
+ block: suspend TestScope.() -> Unit,
+ ) =
+ runTest(dispatcher) {
+ init()
+ promptRepository.setPrompt(promptInfo(), USER_ID, OPERATION_ID, kind)
+ block()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
index e099c9269d3f..ea16cb567028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationUtilsTest.java
@@ -20,6 +20,7 @@ import static com.android.systemui.dreams.complication.Complication.COMPLICATION
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_CAST_INFO;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_DATE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_HOME_CONTROLS;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_SMARTSPACE;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_TIME;
import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_WEATHER;
@@ -63,6 +64,8 @@ public class ComplicationUtilsTest extends SysuiTestCase {
.isEqualTo(COMPLICATION_TYPE_HOME_CONTROLS);
assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_SMARTSPACE))
.isEqualTo(COMPLICATION_TYPE_SMARTSPACE);
+ assertThat(convertComplicationType(DreamBackend.COMPLICATION_TYPE_MEDIA_ENTRY))
+ .isEqualTo(COMPLICATION_TYPE_MEDIA_ENTRY);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
index 50f27ea27ae9..0295030da510 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamMediaEntryComplicationTest.java
@@ -16,8 +16,11 @@
package com.android.systemui.dreams.complication;
+import static com.android.systemui.dreams.complication.Complication.COMPLICATION_TYPE_MEDIA_ENTRY;
import static com.android.systemui.flags.Flags.DREAM_MEDIA_TAP_TO_OPEN;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -32,6 +35,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.complication.dagger.DreamMediaEntryComplicationComponent;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.ui.MediaCarouselController;
import com.android.systemui.media.dream.MediaDreamComplication;
@@ -51,6 +55,9 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper
public class DreamMediaEntryComplicationTest extends SysuiTestCase {
@Mock
+ private DreamMediaEntryComplicationComponent.Factory mComponentFactory;
+
+ @Mock
private View mView;
@Mock
@@ -89,6 +96,14 @@ public class DreamMediaEntryComplicationTest extends SysuiTestCase {
when(mFeatureFlags.isEnabled(DREAM_MEDIA_TAP_TO_OPEN)).thenReturn(false);
}
+ @Test
+ public void testGetRequiredTypeAvailability() {
+ final DreamMediaEntryComplication complication =
+ new DreamMediaEntryComplication(mComponentFactory);
+ assertThat(complication.getRequiredTypeAvailability()).isEqualTo(
+ COMPLICATION_TYPE_MEDIA_ENTRY);
+ }
+
/**
* Ensures clicking media entry chip adds/removes media complication.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 20a82c63cfdd..4b3b70e3ae77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -88,6 +88,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
flagMap,
restarter
)
+ mFeatureFlagsDebug.init()
verify(flagManager).onSettingsChangedAction = any()
broadcastReceiver = withArgCaptor {
verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
@@ -255,11 +256,11 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
broadcastReceiver.onReceive(mockContext, Intent())
broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
- setByBroadcast(0, false) // unknown id does nothing
- setByBroadcast(1, "string") // wrong type does nothing
- setByBroadcast(2, 123) // wrong type does nothing
- setByBroadcast(3, false) // wrong type does nothing
- setByBroadcast(4, 123) // wrong type does nothing
+ setByBroadcast(0, false) // unknown id does nothing
+ setByBroadcast(1, "string") // wrong type does nothing
+ setByBroadcast(2, 123) // wrong type does nothing
+ setByBroadcast(3, false) // wrong type does nothing
+ setByBroadcast(4, 123) // wrong type does nothing
verifyNoMoreInteractions(flagManager, secureSettings)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 575c14262b74..b2dd60c9566d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -38,8 +38,9 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
@Mock private lateinit var mResources: Resources
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
+ @Mock private lateinit var restarter: Restarter
+ private val flagMap = mutableMapOf<Int, Flag<*>>()
private val serverFlagReader = ServerFlagReaderFake()
-
private val deviceConfig = DeviceConfigProxyFake()
@Before
@@ -49,7 +50,9 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
mResources,
mSystemProperties,
deviceConfig,
- serverFlagReader)
+ serverFlagReader,
+ flagMap,
+ restarter)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
new file mode 100644
index 000000000000..6f5f460d41c4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.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.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ServerFlagReaderImplTest : SysuiTestCase() {
+
+ private val NAMESPACE = "test"
+
+ @Mock private lateinit var changeListener: ServerFlagReader.ChangeListener
+
+ private lateinit var serverFlagReader: ServerFlagReaderImpl
+ private val deviceConfig = DeviceConfigProxyFake()
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor)
+ }
+
+ @Test
+ fun testChange_alertsListener() {
+ val flag = ReleasedFlag(1)
+ serverFlagReader.listenForChanges(listOf(flag), changeListener)
+
+ deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
+ executor.runAllReady()
+
+ verify(changeListener).onChange()
+ }
+}
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 4c986bffd172..2c3ddd574b0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -60,6 +60,7 @@ import com.android.systemui.navigationbar.NavigationModeController;
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.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -112,6 +113,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private FalsingCollectorFake mFalsingCollector;
+ private @Mock CentralSurfaces mCentralSurfaces;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -258,6 +261,26 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
verify(mKeyguardStateController).notifyKeyguardGoingAway(false);
}
+ @Test
+ public void testUpdateIsKeyguardAfterOccludeAnimationEnds() {
+ mViewMediator.mOccludeAnimationController.onLaunchAnimationEnd(
+ false /* isExpandingFullyAbove */);
+
+ // Since the updateIsKeyguard call is delayed during the animation, ensure it's called once
+ // it ends.
+ verify(mCentralSurfaces).updateIsKeyguard();
+ }
+
+ @Test
+ public void testUpdateIsKeyguardAfterOccludeAnimationIsCancelled() {
+ mViewMediator.mOccludeAnimationController.onLaunchAnimationCancelled(
+ null /* newKeyguardOccludedState */);
+
+ // Since the updateIsKeyguard call is delayed during the animation, ensure it's called if
+ // it's cancelled.
+ verify(mCentralSurfaces).updateIsKeyguard();
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -287,5 +310,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mNotificationShadeWindowControllerLazy,
() -> mActivityLaunchAnimator);
mViewMediator.start();
+
+ mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
deleted file mode 100644
index 27a5190367a8..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
+++ /dev/null
@@ -1,478 +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.keyguard;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.keyguard.LockIconView.ICON_LOCK;
-import static com.android.keyguard.LockIconView.ICON_UNLOCK;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.AnimatedStateListDrawable;
-import android.hardware.biometrics.BiometricSourceType;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Pair;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.KeyguardViewController;
-import com.android.keyguard.LockIconView;
-import com.android.keyguard.LockIconViewController;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.biometrics.AuthRippleController;
-import com.android.systemui.doze.util.BurnInHelperKt;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class LockIconViewControllerTest extends SysuiTestCase {
- private static final String UNLOCKED_LABEL = "unlocked";
- private static final int PADDING = 10;
-
- private MockitoSession mStaticMockSession;
-
- private @Mock LockIconView mLockIconView;
- private @Mock AnimatedStateListDrawable mIconDrawable;
- private @Mock Context mContext;
- private @Mock Resources mResources;
- private @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager;
- private @Mock StatusBarStateController mStatusBarStateController;
- private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private @Mock KeyguardViewController mKeyguardViewController;
- private @Mock KeyguardStateController mKeyguardStateController;
- private @Mock FalsingManager mFalsingManager;
- private @Mock AuthController mAuthController;
- private @Mock DumpManager mDumpManager;
- private @Mock AccessibilityManager mAccessibilityManager;
- private @Mock ConfigurationController mConfigurationController;
- private @Mock VibratorHelper mVibrator;
- private @Mock AuthRippleController mAuthRippleController;
- private FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock());
-
- private LockIconViewController mLockIconViewController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- private View.OnAttachStateChangeListener mAttachListener;
-
- @Captor private ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor =
- ArgumentCaptor.forClass(KeyguardStateController.Callback.class);
- private KeyguardStateController.Callback mKeyguardStateCallback;
-
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
- private StatusBarStateController.StateListener mStatusBarStateListener;
-
- @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
- private AuthController.Callback mAuthControllerCallback;
-
- @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback>
- mKeyguardUpdateMonitorCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
- private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
-
- @Captor private ArgumentCaptor<Point> mPointCaptor;
-
- @Before
- public void setUp() throws Exception {
- mStaticMockSession = mockitoSession()
- .mockStatic(BurnInHelperKt.class)
- .strictness(Strictness.LENIENT)
- .startMocking();
- MockitoAnnotations.initMocks(this);
-
- setupLockIconViewMocks();
- when(mContext.getResources()).thenReturn(mResources);
- when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
- Rect windowBounds = new Rect(0, 0, 800, 1200);
- when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds);
- when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL);
- when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable);
- when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING);
- when(mAuthController.getScaleFactor()).thenReturn(1f);
-
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-
- mLockIconViewController = new LockIconViewController(
- mLockIconView,
- mStatusBarStateController,
- mKeyguardUpdateMonitor,
- mKeyguardViewController,
- mKeyguardStateController,
- mFalsingManager,
- mAuthController,
- mDumpManager,
- mAccessibilityManager,
- mConfigurationController,
- mDelayableExecutor,
- mVibrator,
- mAuthRippleController,
- mResources
- );
- }
-
- @After
- public void tearDown() {
- mStaticMockSession.finishMocking();
- }
-
- @Test
- public void testUpdateFingerprintLocationOnInit() {
- // GIVEN fp sensor location is available pre-attached
- Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
-
- // WHEN lock icon view controller is initialized and attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN lock icon view location is updated to the udfps location with UDFPS radius
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testUpdatePaddingBasedOnResolutionScale() {
- // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5
- Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location
- when(mAuthController.getScaleFactor()).thenReturn(5f);
-
- // WHEN lock icon view controller is initialized and attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN lock icon view location is updated with the scaled radius
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING * 5));
- }
-
- @Test
- public void testUpdateLockIconLocationOnAuthenticatorsRegistered() {
- // GIVEN fp sensor location is not available pre-init
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
- when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- resetLockIconView(); // reset any method call counts for when we verify method calls later
-
- // GIVEN fp sensor location is available post-attached
- captureAuthControllerCallback();
- Pair<Float, Point> udfps = setupUdfps();
-
- // WHEN all authenticators are registered
- mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT);
- mDelayableExecutor.runAllReady();
-
- // THEN lock icon view location is updated with the same coordinates as auth controller vals
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testUpdateLockIconLocationOnUdfpsLocationChanged() {
- // GIVEN fp sensor location is not available pre-init
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
- when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- resetLockIconView(); // reset any method call counts for when we verify method calls later
-
- // GIVEN fp sensor location is available post-attached
- captureAuthControllerCallback();
- Pair<Float, Point> udfps = setupUdfps();
-
- // WHEN udfps location changes
- mAuthControllerCallback.onUdfpsLocationChanged();
- mDelayableExecutor.runAllReady();
-
- // THEN lock icon view location is updated with the same coordinates as auth controller vals
- verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first),
- eq(PADDING));
- }
-
- @Test
- public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() {
- // GIVEN Udpfs sensor location is available
- setupUdfps();
-
- mLockIconViewController.init();
- captureAttachListener();
-
- // WHEN the view is attached
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon view background should be enabled
- verify(mLockIconView).setUseBackground(true);
- }
-
- @Test
- public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() {
- // GIVEN Udfps sensor location is not supported
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
-
- mLockIconViewController.init();
- captureAttachListener();
-
- // WHEN the view is attached
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon view background should be disabled
- verify(mLockIconView).setUseBackground(false);
- }
-
- @Test
- public void testUnlockIconShows_biometricUnlockedTrue() {
- // GIVEN UDFPS sensor location is available
- setupUdfps();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureKeyguardUpdateMonitorCallback();
-
- // GIVEN user has unlocked with a biometric auth (ie: face auth)
- when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true);
- reset(mLockIconView);
-
- // WHEN face auth's biometric running state changes
- mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false,
- BiometricSourceType.FACE);
-
- // THEN the unlock icon is shown
- verify(mLockIconView).setContentDescription(UNLOCKED_LABEL);
- }
-
- @Test
- public void testLockIconStartState() {
- // GIVEN lock icon state
- setupShowLockIcon();
-
- // WHEN lock icon controller is initialized
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
-
- // THEN the lock icon should show
- verify(mLockIconView).updateIcon(ICON_LOCK, false);
- }
-
- @Test
- public void testLockIcon_updateToUnlock() {
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureKeyguardStateCallback();
- reset(mLockIconView);
-
- // WHEN the unlocked state changes to canDismissLockScreen=true
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
- mKeyguardStateCallback.onUnlockedChanged();
-
- // THEN the unlock should show
- verify(mLockIconView).updateIcon(ICON_UNLOCK, false);
- }
-
- @Test
- public void testLockIcon_clearsIconOnAod_whenUdfpsNotEnrolled() {
- // GIVEN udfps not enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false);
-
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN the dozing state changes
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-
- // THEN the icon is cleared
- verify(mLockIconView).clearIcon();
- }
-
- @Test
- public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() {
- // GIVEN udfps enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
- // GIVEN starting state for the lock icon
- setupShowLockIcon();
-
- // GIVEN lock icon controller is initialized and view is attached
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN the dozing state changes
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
-
- // THEN the AOD lock icon should show
- verify(mLockIconView).updateIcon(ICON_LOCK, true);
- }
-
- @Test
- public void testBurnInOffsetsUpdated_onDozeAmountChanged() {
- // GIVEN udfps enrolled
- setupUdfps();
- when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true);
-
- // GIVEN burn-in offset = 5
- int burnInOffset = 5;
- when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset);
-
- // GIVEN starting state for the lock icon (keyguard)
- setupShowLockIcon();
- mLockIconViewController.init();
- captureAttachListener();
- mAttachListener.onViewAttachedToWindow(mLockIconView);
- captureStatusBarStateListener();
- reset(mLockIconView);
-
- // WHEN dozing updates
- mStatusBarStateListener.onDozingChanged(true /* isDozing */);
- mStatusBarStateListener.onDozeAmountChanged(1f, 1f);
-
- // THEN the view's translation is updated to use the AoD burn-in offsets
- verify(mLockIconView).setTranslationY(burnInOffset);
- verify(mLockIconView).setTranslationX(burnInOffset);
- reset(mLockIconView);
-
- // WHEN the device is no longer dozing
- mStatusBarStateListener.onDozingChanged(false /* isDozing */);
- mStatusBarStateListener.onDozeAmountChanged(0f, 0f);
-
- // THEN the view is updated to NO translation (no burn-in offsets anymore)
- verify(mLockIconView).setTranslationY(0);
- verify(mLockIconView).setTranslationX(0);
-
- }
- private Pair<Float, Point> setupUdfps() {
- when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
- final Point udfpsLocation = new Point(50, 75);
- final float radius = 33f;
- when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation);
- when(mAuthController.getUdfpsRadius()).thenReturn(radius);
-
- return new Pair(radius, udfpsLocation);
- }
-
- private void setupShowLockIcon() {
- when(mKeyguardStateController.isShowing()).thenReturn(true);
- when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false);
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
- when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
- when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false);
- }
-
- private void captureAuthControllerCallback() {
- verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
- mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
- }
-
- private void captureAttachListener() {
- verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachListener = mAttachCaptor.getValue();
- }
-
- private void captureKeyguardStateCallback() {
- verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture());
- mKeyguardStateCallback = mKeyguardStateCaptor.getValue();
- }
-
- private void captureStatusBarStateListener() {
- verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture());
- mStatusBarStateListener = mStatusBarStateCaptor.getValue();
- }
-
- private void captureKeyguardUpdateMonitorCallback() {
- verify(mKeyguardUpdateMonitor).registerCallback(
- mKeyguardUpdateMonitorCallbackCaptor.capture());
- mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue();
- }
-
- private void setupLockIconViewMocks() {
- when(mLockIconView.getResources()).thenReturn(mResources);
- when(mLockIconView.getContext()).thenReturn(mContext);
- }
-
- private void resetLockIconView() {
- reset(mLockIconView);
- setupLockIconViewMocks();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index e99c139e9e7e..f18acbad14f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -15,10 +15,10 @@
*
*/
-package com.android.systemui.keyguard.domain.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import com.android.systemui.animation.Expandable
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.yield
@@ -29,24 +29,27 @@ import kotlinx.coroutines.yield
* This class is abstract to force tests to provide extensions of it as the system that references
* these configs uses each implementation's class type to refer to them.
*/
-abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
+abstract class FakeKeyguardQuickAffordanceConfig(
+ override val key: String,
+) : KeyguardQuickAffordanceConfig {
- var onClickedResult: OnClickedResult = OnClickedResult.Handled
+ var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
- private val _state =
- MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
- KeyguardQuickAffordanceConfig.State.Hidden
+ private val _lockScreenState =
+ MutableStateFlow<KeyguardQuickAffordanceConfig.LockScreenState>(
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
)
- override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+ _lockScreenState
- override fun onQuickAffordanceClicked(
+ override fun onTriggered(
expandable: Expandable?,
- ): OnClickedResult {
- return onClickedResult
+ ): OnTriggeredResult {
+ return onTriggeredResult
}
- suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
- _state.value = state
+ suspend fun setState(lockScreenState: KeyguardQuickAffordanceConfig.LockScreenState) {
+ _lockScreenState.value = lockScreenState
// Yield to allow the test's collection coroutine to "catch up" and collect this value
// before the test continues to the next line.
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 9a91ea91f3a2..c94cec6e313a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -122,8 +122,8 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
emptyList()
}
)
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
if (canShowWhileLocked) {
verify(controlsListingController).addCallback(callbackCaptor.capture())
@@ -139,9 +139,9 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
assertThat(values.last())
.isInstanceOf(
if (isVisibleExpected) {
- KeyguardQuickAffordanceConfig.State.Visible::class.java
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java
} else {
- KeyguardQuickAffordanceConfig.State.Hidden::class.java
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
}
)
job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index a809f0547ee6..659c1e573ca3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -23,7 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
@@ -72,11 +72,11 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
assertThat(values.last())
- .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
job.cancel()
}
@@ -91,31 +91,32 @@ class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
- val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
- val job = underTest.state.onEach(values::add).launchIn(this)
+ val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+ val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
assertThat(values.last())
- .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
job.cancel()
}
@Test
- fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+ fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is true`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
- val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+ val onClickedResult = underTest.onTriggered(expandable)
- assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
- assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+ assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked).isTrue()
}
@Test
- fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+ fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is false`() = runBlockingTest {
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
- val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+ val onClickedResult = underTest.onTriggered(expandable)
- assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
- assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+ assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+ assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked)
+ .isFalse()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 329c4db0a75c..61a3f9f07600 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -1,26 +1,26 @@
/*
- * Copyright (C) 2022 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
@@ -56,9 +56,9 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -77,8 +77,8 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
fun `affordance - scanner activity changed - delivers model with updated intent`() =
runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -93,8 +93,8 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
@@ -109,34 +109,35 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
- var latest: KeyguardQuickAffordanceConfig.State? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
callbackCaptor.value.onQRCodeScannerPreferenceChanged()
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
verify(controller).removeCallback(callbackCaptor.value)
}
@Test
- fun onQuickAffordanceClicked() {
- assertThat(underTest.onQuickAffordanceClicked(mock()))
+ fun onQuickAffordanceTriggered() {
+ assertThat(underTest.onTriggered(mock()))
.isEqualTo(
- OnClickedResult.StartActivity(
+ OnTriggeredResult.StartActivity(
intent = INTENT_1,
canShowWhileLocked = true,
)
)
}
- private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
- assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
- val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+ private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
+ assertThat(latest)
+ .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+ val visibleState = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
assertThat(visibleState.icon).isNotNull()
assertThat(visibleState.icon.contentDescription).isNotNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 98dc4c4f6f76..c05beef6d624 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -1,21 +1,21 @@
/*
- * Copyright (C) 2022 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this 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,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.quickaffordance
+package com.android.systemui.keyguard.data.quickaffordance
import android.graphics.drawable.Drawable
import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -67,11 +67,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
setUpState()
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+ val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
assertThat(visibleModel.icon)
.isEqualTo(
Icon.Loaded(
@@ -88,11 +88,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
setUpState(isWalletEnabled = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -100,11 +100,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - query not successful - model is none`() = runBlockingTest {
setUpState(isWalletQuerySuccessful = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -112,11 +112,11 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - missing icon - model is none`() = runBlockingTest {
setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@@ -124,24 +124,24 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun `affordance - no selected card - model is none`() = runBlockingTest {
setUpState(hasWalletIcon = false)
- var latest: KeyguardQuickAffordanceConfig.State? = null
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.state.onEach { latest = it }.launchIn(this)
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+ assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
job.cancel()
}
@Test
- fun onQuickAffordanceClicked() {
+ fun onQuickAffordanceTriggered() {
val animationController: ActivityLaunchAnimator.Controller = mock()
val expandable: Expandable = mock {
whenever(this.activityLaunchController()).thenReturn(animationController)
}
- assertThat(underTest.onQuickAffordanceClicked(expandable))
- .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+ assertThat(underTest.onTriggered(expandable))
+ .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled)
verify(walletController)
.startQuickAccessUiIntent(
activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index b4d5464d1177..7116cc101d3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,11 +25,12 @@ import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -211,7 +212,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -224,8 +229,14 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
- object : FakeKeyguardQuickAffordanceConfig() {},
- object : FakeKeyguardQuickAffordanceConfig() {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {},
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {},
),
),
),
@@ -237,30 +248,30 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
}
@Test
- fun onQuickAffordanceClicked() = runBlockingTest {
+ fun onQuickAffordanceTriggered() = runBlockingTest {
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
)
homeControls.setState(
- state =
- KeyguardQuickAffordanceConfig.State.Visible(
+ lockScreenState =
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = DRAWABLE,
)
)
- homeControls.onClickedResult =
+ homeControls.onTriggeredResult =
if (startActivity) {
- KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = INTENT,
canShowWhileLocked = canShowWhileLocked,
)
} else {
- KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
- underTest.onQuickAffordanceClicked(
- configKey = homeControls::class,
+ underTest.onQuickAffordanceTriggered(
+ configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
expandable = expandable,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 65fd6e576650..ae32ba6676be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,13 +22,14 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -69,9 +70,21 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true)
- homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControls =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWallet =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScanner =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
underTest =
KeyguardQuickAffordanceInteractor(
@@ -99,11 +112,11 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@Test
fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
- val configKey = homeControls::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
- toggle = KeyguardQuickAffordanceToggleState.On,
+ activationState = ActivationState.Active,
)
)
@@ -124,15 +137,15 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On)
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
job.cancel()
}
@Test
fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
- val configKey = quickAccessWallet::class
+ val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
quickAccessWallet.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
@@ -154,7 +167,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
assertThat(visibleModel.icon).isEqualTo(ICON)
assertThat(visibleModel.icon.contentDescription)
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
- assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported)
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
job.cancel()
}
@@ -162,7 +175,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
repository.setDozing(true)
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
@@ -182,7 +195,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
runBlockingTest {
repository.setKeyguardShowing(false)
homeControls.setState(
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = ICON,
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
index e68c43f4abd7..13e2768e1fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/quickaffordance/FakeKeyguardQuickAffordanceRegistry.kt
@@ -17,8 +17,8 @@
package com.android.systemui.keyguard.domain.quickaffordance
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import kotlin.reflect.KClass
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
class FakeKeyguardQuickAffordanceRegistry(
@@ -33,11 +33,8 @@ class FakeKeyguardQuickAffordanceRegistry(
}
override fun get(
- configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
+ key: String,
): FakeKeyguardQuickAffordanceConfig {
- return configsByPosition.values
- .flatten()
- .associateBy { config -> config::class }
- .getValue(configClass)
+ return configsByPosition.values.flatten().associateBy { config -> config.key }.getValue(key)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index d674c89c0e14..f73d1ecf9373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,15 +23,16 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
-import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -40,7 +41,6 @@ import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
-import kotlin.reflect.KClass
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runBlockingTest
@@ -81,9 +81,21 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
.thenReturn(RETURNED_BURN_IN_OFFSET)
- homeControlsQuickAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- quickAccessWalletAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
- qrCodeScannerAffordanceConfig = object : FakeKeyguardQuickAffordanceConfig() {}
+ homeControlsQuickAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
+ ) {}
+ quickAccessWalletAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+ ) {}
+ qrCodeScannerAffordanceConfig =
+ object :
+ FakeKeyguardQuickAffordanceConfig(
+ BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
+ ) {}
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
@@ -489,42 +501,42 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition,
testConfig: TestConfig,
- ): KClass<out FakeKeyguardQuickAffordanceConfig> {
+ ): String {
val config =
when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
}
- val state =
+ val lockScreenState =
if (testConfig.isVisible) {
if (testConfig.intent != null) {
- config.onClickedResult =
- KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+ config.onTriggeredResult =
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
intent = testConfig.intent,
canShowWhileLocked = testConfig.canShowWhileLocked,
)
}
- KeyguardQuickAffordanceConfig.State.Visible(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
- toggle =
+ activationState =
when (testConfig.isActivated) {
- true -> KeyguardQuickAffordanceToggleState.On
- false -> KeyguardQuickAffordanceToggleState.Off
- null -> KeyguardQuickAffordanceToggleState.NotSupported
+ true -> ActivationState.Active
+ false -> ActivationState.Inactive
+ null -> ActivationState.NotSupported
}
)
} else {
- KeyguardQuickAffordanceConfig.State.Hidden
+ KeyguardQuickAffordanceConfig.LockScreenState.Hidden
}
- config.setState(state)
- return config::class
+ config.setState(lockScreenState)
+ return config.key
}
private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig,
- configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
+ configKey: String,
) {
checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 071604dc5790..920801f95f5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -31,7 +31,7 @@ import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.dream.MediaDreamComplication
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.testing.FakeNotifPanelEvents
+import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -89,7 +89,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
- private val notifPanelEvents = FakeNotifPanelEvents()
+ private val notifPanelEvents = ShadeExpansionStateManager()
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
@@ -346,7 +346,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
@Test
fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
- notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+ notifPanelEvents.notifyExpandImmediateChange(true)
goToLockscreen()
enterGuidedTransformation()
whenever(lockHost.visible).thenReturn(true)
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 fdeb3f5eb857..ad19bc2a80e0 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
@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -80,6 +81,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var chipbarLogger: ChipbarLogger
@Mock private lateinit var logger: MediaTttLogger
@Mock private lateinit var mediaTttFlags: MediaTttFlags
@Mock private lateinit var packageManager: PackageManager
@@ -122,7 +124,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
chipbarCoordinator =
FakeChipbarCoordinator(
context,
- logger,
+ chipbarLogger,
windowManager,
fakeExecutor,
accessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
new file mode 100644
index 000000000000..f20c6a29b840
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.content.Intent
+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.util.mockito.whenever
+import com.android.wm.shell.floating.FloatingTasks
+import java.util.*
+import org.junit.Before
+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.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskController].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskControllerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskControllerTest : SysuiTestCase() {
+
+ private val notesIntent = Intent(NOTES_ACTION)
+
+ @Mock lateinit var context: Context
+ @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
+ @Mock lateinit var floatingTasks: FloatingTasks
+ @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+ @Mock lateinit var keyguardManager: KeyguardManager
+ @Mock lateinit var optionalKeyguardManager: Optional<KeyguardManager>
+ @Mock lateinit var optionalUserManager: Optional<UserManager>
+ @Mock lateinit var userManager: UserManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
+ whenever(optionalUserManager.orElse(null)).thenReturn(userManager)
+ whenever(userManager.isUserUnlocked).thenReturn(true)
+ }
+
+ private fun createNoteTaskController(isEnabled: Boolean = true): NoteTaskController {
+ return NoteTaskController(
+ context = context,
+ intentResolver = noteTaskIntentResolver,
+ optionalFloatingTasks = optionalFloatingTasks,
+ optionalKeyguardManager = optionalKeyguardManager,
+ optionalUserManager = optionalUserManager,
+ isEnabled = isEnabled,
+ )
+ }
+
+ @Test
+ fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(floatingTasks).showOrSetStashed(notesIntent)
+ verify(context, never()).startActivity(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() {
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+ whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+ whenever(optionalUserManager.orElse(null)).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+ whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_flagDisabled_shouldDoNothing() {
+ createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+
+ @Test
+ fun handleSystemKey_userIsLocked_shouldDoNothing() {
+ whenever(userManager.isUserUnlocked).thenReturn(false)
+
+ createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(context, never()).startActivity(notesIntent)
+ verify(floatingTasks, never()).showOrSetStashed(notesIntent)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
new file mode 100644
index 000000000000..f344c8d9eec4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.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.notetask
+
+import android.test.suitebuilder.annotation.SmallTest
+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.floating.FloatingTasks
+import java.util.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskController].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskInitializerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskInitializerTest : SysuiTestCase() {
+
+ @Mock lateinit var commandQueue: CommandQueue
+ @Mock lateinit var floatingTasks: FloatingTasks
+ @Mock lateinit var optionalFloatingTasks: Optional<FloatingTasks>
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(optionalFloatingTasks.isPresent).thenReturn(true)
+ whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks)
+ }
+
+ private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
+ return NoteTaskInitializer(
+ optionalFloatingTasks = optionalFloatingTasks,
+ lazyNoteTaskController = mock(),
+ commandQueue = commandQueue,
+ isEnabled = isEnabled,
+ )
+ }
+
+ @Test
+ fun initialize_shouldAddCallbacks() {
+ createNoteTaskInitializer().initialize()
+
+ verify(commandQueue).addCallback(any())
+ }
+
+ @Test
+ fun initialize_flagDisabled_shouldDoNothing() {
+ createNoteTaskInitializer(isEnabled = false).initialize()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+
+ @Test
+ fun initialize_floatingTasksNotPresent_shouldDoNothing() {
+ whenever(optionalFloatingTasks.isPresent).thenReturn(false)
+
+ createNoteTaskInitializer().initialize()
+
+ verify(commandQueue, never()).addCallback(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
new file mode 100644
index 000000000000..dd2cc2ffc9db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskIntentResolverTest.kt
@@ -0,0 +1,222 @@
+/*
+ * 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
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.content.pm.ServiceInfo
+import android.test.suitebuilder.annotation.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [NoteTaskIntentResolver].
+ *
+ * Build/Install/Run:
+ * - atest SystemUITests:NoteTaskIntentResolverTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskIntentResolverTest : SysuiTestCase() {
+
+ @Mock lateinit var packageManager: PackageManager
+
+ private lateinit var resolver: NoteTaskIntentResolver
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ resolver = NoteTaskIntentResolver(packageManager)
+ }
+
+ private fun createResolveInfo(
+ packageName: String = "PackageName",
+ activityInfo: ActivityInfo? = null,
+ ): ResolveInfo {
+ return ResolveInfo().apply {
+ serviceInfo =
+ ServiceInfo().apply {
+ applicationInfo = ApplicationInfo().apply { this.packageName = packageName }
+ }
+ this.activityInfo = activityInfo
+ }
+ }
+
+ private fun createActivityInfo(
+ name: String? = "ActivityName",
+ exported: Boolean = true,
+ enabled: Boolean = true,
+ showWhenLocked: Boolean = true,
+ turnScreenOn: Boolean = true,
+ ): ActivityInfo {
+ return ActivityInfo().apply {
+ this.name = name
+ this.exported = exported
+ this.enabled = enabled
+ if (showWhenLocked) {
+ flags = flags or ActivityInfo.FLAG_SHOW_WHEN_LOCKED
+ }
+ if (turnScreenOn) {
+ flags = flags or ActivityInfo.FLAG_TURN_SCREEN_ON
+ }
+ }
+ }
+
+ private fun givenQueryIntentActivities(block: () -> List<ResolveInfo>) {
+ whenever(packageManager.queryIntentActivities(any(), any<ResolveInfoFlags>()))
+ .thenReturn(block())
+ }
+
+ private fun givenResolveActivity(block: () -> ResolveInfo?) {
+ whenever(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenReturn(block())
+ }
+
+ @Test
+ fun resolveIntent_shouldReturnNotesIntent() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo()) }
+
+ val actual = resolver.resolveIntent()
+
+ val expected =
+ Intent(NOTES_ACTION)
+ .setComponent(ComponentName("PackageName", "ActivityName"))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ // Compares the string representation of both intents, as they are different instances.
+ assertThat(actual.toString()).isEqualTo(expected.toString())
+ }
+
+ @Test
+ fun resolveIntent_activityInfoEnabledIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(enabled = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoExportedIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(exported = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoShowWhenLockedIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(showWhenLocked = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoTurnScreenOnIsFalse_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity {
+ createResolveInfo(activityInfo = createActivityInfo(turnScreenOn = false))
+ }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoNameIsBlank_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = "")) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoNameIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = createActivityInfo(name = null)) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityInfoIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { createResolveInfo(activityInfo = null) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_resolveActivityIsNull_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo()) }
+ givenResolveActivity { null }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_packageNameIsBlank_shouldReturnNull() {
+ givenQueryIntentActivities { listOf(createResolveInfo(packageName = "")) }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+
+ @Test
+ fun resolveIntent_activityNotFoundForAction_shouldReturnNull() {
+ givenQueryIntentActivities { emptyList() }
+
+ val actual = resolver.resolveIntent()
+
+ assertThat(actual).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
index bc27bbc13f81..3131f60893c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.UserSwitcherController
import com.android.systemui.user.data.source.UserRecord
import org.junit.Assert.assertEquals
@@ -41,20 +42,27 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class UserDetailViewAdapterTest : SysuiTestCase() {
- @Mock private lateinit var mUserSwitcherController: UserSwitcherController
- @Mock private lateinit var mParent: ViewGroup
- @Mock private lateinit var mUserDetailItemView: UserDetailItemView
- @Mock private lateinit var mOtherView: View
- @Mock private lateinit var mInflatedUserDetailItemView: UserDetailItemView
- @Mock private lateinit var mLayoutInflater: LayoutInflater
+ @Mock
+ private lateinit var mUserSwitcherController: UserSwitcherController
+ @Mock
+ private lateinit var mParent: ViewGroup
+ @Mock
+ private lateinit var mUserDetailItemView: UserDetailItemView
+ @Mock
+ private lateinit var mOtherView: View
+ @Mock
+ private lateinit var mInflatedUserDetailItemView: UserDetailItemView
+ @Mock
+ private lateinit var mLayoutInflater: LayoutInflater
private var falsingManagerFake: FalsingManagerFake = FalsingManagerFake()
private lateinit var adapter: UserDetailView.Adapter
private lateinit var uiEventLogger: UiEventLoggerFake
@@ -67,10 +75,12 @@ class UserDetailViewAdapterTest : SysuiTestCase() {
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater)
`when`(mLayoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean()))
- .thenReturn(mInflatedUserDetailItemView)
+ .thenReturn(mInflatedUserDetailItemView)
`when`(mParent.context).thenReturn(mContext)
- adapter = UserDetailView.Adapter(mContext, mUserSwitcherController, uiEventLogger,
- falsingManagerFake)
+ adapter = UserDetailView.Adapter(
+ mContext, mUserSwitcherController, uiEventLogger,
+ falsingManagerFake
+ )
mPicture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user))
}
@@ -145,6 +155,15 @@ class UserDetailViewAdapterTest : SysuiTestCase() {
assertNull(adapter.users.find { it.isManageUsers })
}
+ @Test
+ fun clickDismissDialog() {
+ val shower: UserSwitchDialogController.DialogShower =
+ mock(UserSwitchDialogController.DialogShower::class.java)
+ adapter.injectDialogShower(shower)
+ adapter.onUserListItemClicked(createUserRecord(current = true, guest = false), shower)
+ verify(shower).dismiss()
+ }
+
private fun createUserRecord(current: Boolean, guest: Boolean) =
UserRecord(
UserInfo(0 /* id */, "name", 0 /* flags */),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index d70370552bb1..2ef731236851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -5,6 +5,7 @@ import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT;
import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX;
@@ -13,6 +14,7 @@ import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -38,6 +40,7 @@ import android.net.wifi.WifiManager;
import android.os.Handler;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
@@ -57,6 +60,8 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.UnreleasedFlag;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.connectivity.AccessPointController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -70,12 +75,15 @@ import com.android.systemui.util.time.FakeSystemClock;
import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.ArrayList;
import java.util.List;
@@ -86,6 +94,9 @@ import java.util.List;
public class InternetDialogControllerTest extends SysuiTestCase {
private static final int SUB_ID = 1;
+ private static final int SUB_ID2 = 2;
+
+ private MockitoSession mStaticMockSession;
//SystemUIToast
private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
@@ -158,6 +169,8 @@ public class InternetDialogControllerTest extends SysuiTestCase {
private WifiStateWorker mWifiStateWorker;
@Mock
private SignalStrength mSignalStrength;
+ @Mock
+ private FeatureFlags mFlags;
private TestableResources mTestableResources;
private InternetDialogController mInternetDialogController;
@@ -167,6 +180,10 @@ public class InternetDialogControllerTest extends SysuiTestCase {
@Before
public void setUp() {
+ mStaticMockSession = mockitoSession()
+ .mockStatic(SubscriptionManager.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
MockitoAnnotations.initMocks(this);
mTestableResources = mContext.getOrCreateTestableResources();
doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
@@ -183,6 +200,7 @@ public class InternetDialogControllerTest extends SysuiTestCase {
mAccessPoints.add(mWifiEntry1);
when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID);
when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
.thenReturn(mSystemUIToast);
when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -196,7 +214,7 @@ public class InternetDialogControllerTest extends SysuiTestCase {
mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher,
mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
- mLocationController, mDialogLaunchAnimator, mWifiStateWorker);
+ mLocationController, mDialogLaunchAnimator, mWifiStateWorker, mFlags);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDialogController.mOnSubscriptionsChangedListener);
mInternetDialogController.onStart(mInternetDialogCallback, true);
@@ -205,6 +223,11 @@ public class InternetDialogControllerTest extends SysuiTestCase {
mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
}
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
@Test
public void connectCarrierNetwork_mergedCarrierEntryCanConnect_connectAndCreateSysUiToast() {
when(mTelephonyManager.isDataEnabled()).thenReturn(true);
@@ -387,15 +410,45 @@ public class InternetDialogControllerTest extends SysuiTestCase {
@Test
public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
fakeAirplaneModeEnabled(false);
when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
- mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+ spyController.onAccessPointsChanged(null /* accessPoints */);
+ doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
doReturn(mServiceState).when(mTelephonyManager).getServiceState();
doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
- assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ assertFalse(TextUtils.equals(spyController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+
+ doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.onAccessPointsChanged(null /* accessPoints */);
+ assertTrue(TextUtils.equals(spyController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+ }
+
+ @Test
+ public void getSubtitleText_withNoService_returnNoNetworksAvailable_flagOff() {
+ InternetDialogController spyController = spy(mInternetDialogController);
+ fakeAirplaneModeEnabled(false);
+ when(mWifiStateWorker.isWifiEnabled()).thenReturn(true);
+ spyController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+ doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
+
+ assertTrue(TextUtils.equals(spyController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+
+ doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.onAccessPointsChanged(null /* accessPoints */);
+ assertTrue(TextUtils.equals(spyController.getSubtitleText(false),
getResourcesString("all_network_unavailable")));
}
@@ -713,6 +766,108 @@ public class InternetDialogControllerTest extends SysuiTestCase {
}
@Test
+ public void getSignalStrengthIcon_differentSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ Drawable icons = spyController.getSignalStrengthIcon(SUB_ID, mContext, 1, 1, 0, false);
+ Drawable icons2 = spyController.getSignalStrengthIcon(SUB_ID2, mContext, 1, 1, 0, false);
+
+ assertThat(icons).isNotEqualTo(icons2);
+ }
+
+ @Test
+ public void getActiveAutoSwitchNonDdsSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ // active on non-DDS
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ doReturn(SUB_ID2).when(info).getSubscriptionId();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ int subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SUB_ID2);
+
+ // active on CBRS
+ doReturn(true).when(info).isOpportunistic();
+ subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+ // active on DDS
+ doReturn(false).when(info).isOpportunistic();
+ doReturn(SUB_ID).when(info).getSubscriptionId();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ @Test
+ public void getActiveAutoSwitchNonDdsSubId_flagOff() {
+ // active on non-DDS
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ doReturn(SUB_ID2).when(info).getSubscriptionId();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ int subId = mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+ assertThat(subId).isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ @Test
+ public void getMobileNetworkSummary() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
+ doReturn(true).when(spyController).isMobileDataEnabled();
+ doReturn(true).when(spyController).activeNetworkIsCellular();
+ String dds = spyController.getMobileNetworkSummary(SUB_ID);
+ String nonDds = spyController.getMobileNetworkSummary(SUB_ID2);
+
+ assertThat(dds).contains(mContext.getString(R.string.mobile_data_poor_connection));
+ assertThat(dds).isNotEqualTo(nonDds);
+ }
+
+ @Test
+ public void getMobileNetworkSummary_flagOff() {
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(true).when(spyController).isMobileDataEnabled();
+ doReturn(true).when(spyController).activeNetworkIsCellular();
+ String dds = spyController.getMobileNetworkSummary(SUB_ID);
+
+ assertThat(dds).contains(mContext.getString(R.string.mobile_data_connection_active));
+ }
+
+ @Test
+ public void launchMobileNetworkSettings_validSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(SUB_ID2).when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.launchMobileNetworkSettings(mDialogLaunchView);
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt(),
+ any());
+ }
+
+ @Test
+ public void launchMobileNetworkSettings_invalidSubId() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ InternetDialogController spyController = spy(mInternetDialogController);
+ doReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .when(spyController).getActiveAutoSwitchNonDdsSubId();
+ spyController.launchMobileNetworkSettings(mDialogLaunchView);
+
+ verify(mActivityStarter, never())
+ .postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void setAutoDataSwitchMobileDataPolicy() {
+ when(mFlags.isEnabled(any(UnreleasedFlag.class))).thenReturn(true);
+ mInternetDialogController.setAutoDataSwitchMobileDataPolicy(SUB_ID, true);
+
+ verify(mTelephonyManager).setMobileDataPolicyEnabled(eq(
+ TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH), eq(true));
+ }
+
+ @Test
public void getSignalStrengthDrawableWithLevel_carrierNetworkIsNotActive_useMobileDataLevel() {
// Fake mobile data level as SIGNAL_STRENGTH_POOR(1)
when(mSignalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_POOR);
@@ -720,9 +875,9 @@ public class InternetDialogControllerTest extends SysuiTestCase {
when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX);
InternetDialogController spyController = spy(mInternetDialogController);
- spyController.getSignalStrengthDrawableWithLevel(false /* isCarrierNetworkActive */);
+ spyController.getSignalStrengthDrawableWithLevel(false /* isCarrierNetworkActive */, 0);
- verify(spyController).getSignalStrengthIcon(any(), eq(SIGNAL_STRENGTH_POOR),
+ verify(spyController).getSignalStrengthIcon(eq(0), any(), eq(SIGNAL_STRENGTH_POOR),
eq(NUM_SIGNAL_STRENGTH_BINS), anyInt(), anyBoolean());
}
@@ -734,9 +889,9 @@ public class InternetDialogControllerTest extends SysuiTestCase {
when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX);
InternetDialogController spyController = spy(mInternetDialogController);
- spyController.getSignalStrengthDrawableWithLevel(true /* isCarrierNetworkActive */);
+ spyController.getSignalStrengthDrawableWithLevel(true /* isCarrierNetworkActive */, 0);
- verify(spyController).getSignalStrengthIcon(any(), eq(WIFI_LEVEL_MAX),
+ verify(spyController).getSignalStrengthIcon(eq(0), any(), eq(WIFI_LEVEL_MAX),
eq(WIFI_LEVEL_MAX + 1), anyInt(), anyBoolean());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index f92247580df0..4084cf44446d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -8,12 +8,15 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
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 android.app.AlertDialog;
+import android.content.DialogInterface;
import android.os.Handler;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
@@ -31,6 +34,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,6 +76,8 @@ public class InternetDialogTest extends SysuiTestCase {
private InternetDialogController mInternetDialogController;
@Mock
private KeyguardStateController mKeyguard;
+ @Mock
+ private DialogLaunchAnimator mDialogLaunchAnimator;
private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock());
private InternetDialog mInternetDialog;
@@ -100,8 +106,9 @@ public class InternetDialogTest extends SysuiTestCase {
when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
when(mWifiEntries.size()).thenReturn(1);
- when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE);
- when(mInternetDialogController.getMobileNetworkSummary())
+ when(mInternetDialogController.getMobileNetworkTitle(anyInt()))
+ .thenReturn(MOBILE_NETWORK_TITLE);
+ when(mInternetDialogController.getMobileNetworkSummary(anyInt()))
.thenReturn(MOBILE_NETWORK_SUMMARY);
when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
@@ -115,7 +122,8 @@ public class InternetDialogTest extends SysuiTestCase {
private void createInternetDialog() {
mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
- mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler,
+ mInternetDialogController, true, true, true, mock(UiEventLogger.class),
+ mDialogLaunchAnimator, mHandler,
mBgExecutor, mKeyguard);
mInternetDialog.mAdapter = mInternetAdapter;
mInternetDialog.mConnectedWifiEntry = mInternetWifiEntry;
@@ -307,12 +315,18 @@ public class InternetDialogTest extends SysuiTestCase {
@Test
public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
+ mInternetDialog.dismissDialog();
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ createInternetDialog();
// The preconditions WiFi ON and Internet WiFi are already in setUp()
doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
- mInternetDialog.updateDialog(false);
+ mInternetDialog.updateDialog(true);
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+ LinearLayout secondaryLayout = mDialogView.requireViewById(
+ R.id.secondary_mobile_network_layout);
+ assertThat(secondaryLayout.getVisibility()).isEqualTo(View.GONE);
}
@Test
@@ -460,6 +474,44 @@ public class InternetDialogTest extends SysuiTestCase {
}
@Test
+ public void updateDialog_showSecondaryDataSub() {
+ mInternetDialog.dismissDialog();
+ doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
+ doReturn(true).when(mInternetDialogController).hasActiveSubId();
+ doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
+ createInternetDialog();
+
+ clearInvocations(mInternetDialogController);
+ mInternetDialog.updateDialog(true);
+
+ LinearLayout primaryLayout = mDialogView.requireViewById(
+ R.id.mobile_network_layout);
+ LinearLayout secondaryLayout = mDialogView.requireViewById(
+ R.id.secondary_mobile_network_layout);
+
+ verify(mInternetDialogController).getMobileNetworkSummary(1);
+ assertThat(primaryLayout.getBackground()).isNotEqualTo(secondaryLayout.getBackground());
+
+ // Tap the primary sub info
+ primaryLayout.performClick();
+ ArgumentCaptor<AlertDialog> dialogArgumentCaptor =
+ ArgumentCaptor.forClass(AlertDialog.class);
+ verify(mDialogLaunchAnimator).showFromDialog(dialogArgumentCaptor.capture(),
+ eq(mInternetDialog), eq(null), eq(false));
+ AlertDialog dialog = dialogArgumentCaptor.getValue();
+ dialog.show();
+ dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
+ TestableLooper.get(this).processAllMessages();
+ verify(mInternetDialogController).setAutoDataSwitchMobileDataPolicy(1, false);
+
+ // Tap the secondary sub info
+ secondaryLayout.performClick();
+ verify(mInternetDialogController).launchMobileNetworkSettings(any(View.class));
+
+ dialog.dismiss();
+ }
+
+ @Test
public void updateDialog_wifiOn_hideWifiScanNotify() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
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 02f28a235b95..ac4dd49208c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -438,8 +438,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
mMainHandler = new Handler(Looper.getMainLooper());
- NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
- new NotificationPanelViewController.PanelEventsEmitter();
mNotificationPanelViewController = new NotificationPanelViewController(
mView,
@@ -495,7 +493,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
() -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
mNotificationListContainer,
- panelEventsEmitter,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
@@ -1597,7 +1594,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor).requestFaceAuth(true,
+ verify(mUpdateMonitor).requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
}
@@ -1607,7 +1604,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
+ when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1622,7 +1619,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.mStatusBarStateListener;
statusBarStateListener.onStateChanged(KEYGUARD);
mNotificationPanelViewController.setDozing(false, false);
- when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
+ when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
// This sets the dozing state that is read when onMiddleClicked is eventually invoked.
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1642,7 +1639,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
}
@Test
@@ -1653,7 +1650,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
- verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+ verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
deleted file mode 100644
index d05213877232..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
+++ /dev/null
@@ -1,37 +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.shade.testing
-
-import com.android.systemui.shade.NotifPanelEvents
-
-/** Fake implementation of [NotifPanelEvents] for testing. */
-class FakeNotifPanelEvents : NotifPanelEvents {
-
- private val listeners = mutableListOf<NotifPanelEvents.Listener>()
-
- override fun registerListener(listener: NotifPanelEvents.Listener) {
- listeners.add(listener)
- }
-
- override fun unregisterListener(listener: NotifPanelEvents.Listener) {
- listeners.remove(listener)
- }
-
- fun changeExpandImmediate(expandImmediate: Boolean) {
- listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ffb41e5378bd..70cbc64c79f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -19,6 +19,7 @@ import android.content.ContentResolver
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Handler
+import android.os.UserHandle
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -104,13 +105,14 @@ class ClockRegistryTest : SysuiTestCase() {
mockContext,
mockPluginManager,
mockHandler,
- fakeDefaultProvider
+ isEnabled = true,
+ userHandle = UserHandle.USER_ALL,
+ defaultClockProvider = fakeDefaultProvider
) {
override var currentClockId: ClockId
get() = settingValue
set(value) { settingValue = value }
}
- registry.isEnabled = true
verify(mockPluginManager)
.addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 64dc9568030b..4478039912c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
+import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -69,10 +70,13 @@ public class RemoteTransitionTest extends SysuiTestCase {
TransitionInfo combined = new TransitionInfoBuilder(TRANSIT_CLOSE)
.addChange(TRANSIT_CHANGE, FLAG_SHOW_WALLPAPER,
createTaskInfo(1 /* taskId */, ACTIVITY_TYPE_STANDARD))
+ // Embedded TaskFragment should be excluded when animated with Task.
+ .addChange(TRANSIT_CLOSE, FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, null /* taskInfo */)
.addChange(TRANSIT_CLOSE, 0 /* flags */,
createTaskInfo(2 /* taskId */, ACTIVITY_TYPE_STANDARD))
.addChange(TRANSIT_OPEN, FLAG_IS_WALLPAPER, null /* taskInfo */)
- .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */).build();
+ .addChange(TRANSIT_CHANGE, FLAG_FIRST_CUSTOM, null /* taskInfo */)
+ .build();
// Check apps extraction
RemoteAnimationTarget[] wrapped = RemoteAnimationTargetCompat.wrapApps(combined,
mock(SurfaceControl.Transaction.class), null /* leashes */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index c961cec39208..b4a5f5ce205f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -36,7 +36,8 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -71,12 +72,12 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
- @Mock private NotifPanelEvents mNotifPanelEvents;
+ @Mock private ShadeStateEvents mShadeStateEvents;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
- @Captor private ArgumentCaptor<NotifPanelEvents.Listener> mNotifPanelEventsCallbackCaptor;
+ @Captor private ArgumentCaptor<ShadeStateEventsListener> mNotifPanelEventsCallbackCaptor;
@Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -84,7 +85,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
private WakefulnessLifecycle.Observer mWakefulnessObserver;
private StatusBarStateController.StateListener mStatusBarStateListener;
- private NotifPanelEvents.Listener mNotifPanelEventsCallback;
+ private ShadeStateEvents.ShadeStateEventsListener mNotifPanelEventsCallback;
private NotifStabilityManager mNotifStabilityManager;
private NotificationEntry mEntry;
private GroupEntry mGroupEntry;
@@ -97,7 +98,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
mFakeExecutor,
mDumpManager,
mHeadsUpManager,
- mNotifPanelEvents,
+ mShadeStateEvents,
mStatusBarStateController,
mVisualStabilityProvider,
mWakefulnessLifecycle);
@@ -111,7 +112,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
mStatusBarStateListener = mSBStateListenerCaptor.getValue();
- verify(mNotifPanelEvents).registerListener(mNotifPanelEventsCallbackCaptor.capture());
+ verify(mShadeStateEvents).addShadeStateEventsListener(
+ mNotifPanelEventsCallbackCaptor.capture());
mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue();
verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
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 91aecd8cf753..dceb4ff48125 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
@@ -78,6 +78,7 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,6 +90,7 @@ import org.mockito.junit.MockitoRule;
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
+@Ignore("b/255552856")
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
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 6de8bd5f3670..57557821cca6 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
@@ -235,6 +235,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NavigationBarController mNavigationBarController;
@Mock private AccessibilityFloatingMenuController mAccessibilityFloatingMenuController;
@Mock private SysuiColorExtractor mColorExtractor;
+ private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock private ColorExtractor.GradientColors mGradientColors;
@Mock private PulseExpansionHandler mPulseExpansionHandler;
@Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@@ -366,10 +367,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
return null;
}).when(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any());
- WakefulnessLifecycle wakefulnessLifecycle =
+ mWakefulnessLifecycle =
new WakefulnessLifecycle(mContext, mIWallpaperManager, mDumpManager);
- wakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
- wakefulnessLifecycle.dispatchFinishedWakingUp();
+ mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+ mWakefulnessLifecycle.dispatchFinishedWakingUp();
when(mGradientColors.supportsDarkText()).thenReturn(true);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
@@ -428,7 +429,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mBatteryController,
mColorExtractor,
new ScreenLifecycle(mDumpManager),
- wakefulnessLifecycle,
+ mWakefulnessLifecycle,
mStatusBarStateController,
Optional.of(mBubbles),
mDeviceProvisionedController,
@@ -507,6 +508,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController;
mCentralSurfaces.mBarService = mBarService;
mCentralSurfaces.mStackScroller = mStackScroller;
+ mCentralSurfaces.mGestureWakeLock = mPowerManager.newWakeLock(
+ PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "sysui:GestureWakeLock");
mCentralSurfaces.startKeyguard();
mInitController.executePostInitTasks();
notificationLogger.setUpWithContainer(mNotificationListContainer);
@@ -1125,6 +1128,55 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
assertThat(onDismissActionCaptor.getValue().onDismiss()).isFalse();
}
+ @Test
+ public void testKeyguardHideDelayedIfOcclusionAnimationRunning() {
+ // Show the keyguard and verify we've done so.
+ setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.KEYGUARD);
+
+ // Request to hide the keyguard, but while the occlude animation is playing. We should delay
+ // this hide call, since we're playing the occlude animation over the keyguard and thus want
+ // it to remain visible.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController, never()).setState(StatusBarState.SHADE);
+
+ // Once the animation ends, verify that the keyguard is actually hidden.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.SHADE);
+ }
+
+ @Test
+ public void testKeyguardHideNotDelayedIfOcclusionAnimationNotRunning() {
+ // Show the keyguard and verify we've done so.
+ setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.KEYGUARD);
+
+ // Hide the keyguard while the occlusion animation is not running. Verify that we
+ // immediately hide the keyguard.
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(false);
+ setKeyguardShowingAndOccluded(false /* showing */, true /* occluded */);
+ verify(mStatusBarStateController).setState(StatusBarState.SHADE);
+ }
+
+ /**
+ * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
+ * to reconfigure the keyguard to reflect the requested showing/occluded states.
+ */
+ private void setKeyguardShowingAndOccluded(boolean showing, boolean occluded) {
+ when(mStatusBarStateController.isKeyguardRequested()).thenReturn(showing);
+ when(mKeyguardStateController.isOccluded()).thenReturn(occluded);
+
+ // If we want to show the keyguard, make sure that we think we're awake and not unlocking.
+ if (showing) {
+ when(mBiometricUnlockController.isWakeAndUnlock()).thenReturn(false);
+ mWakefulnessLifecycle.dispatchStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+ }
+
+ mCentralSurfaces.updateIsKeyguard(false /* forceStateChange */);
+ }
+
private void setDeviceState(int state) {
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor =
ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4d1a52c494ab..a5deaa45bf0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
@@ -58,6 +59,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -117,6 +119,7 @@ public class ScrimControllerTest extends SysuiTestCase {
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private KeyguardViewMediator mKeyguardViewMediator;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -230,7 +233,8 @@ public class ScrimControllerTest extends SysuiTestCase {
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mKeyguardViewMediator);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -239,6 +243,8 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimController.setWallpaperSupportsAmbientMode(false);
mScrimController.transitionTo(ScrimState.KEYGUARD);
finishAnimationsImmediately();
+
+ mScrimController.setLaunchingAffordanceWithPreview(false);
}
@After
@@ -852,7 +858,8 @@ public class ScrimControllerTest extends SysuiTestCase {
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mKeyguardViewMediator);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1592,6 +1599,30 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimAlpha(mScrimBehind, 0);
}
+ @Test
+ public void keyguardAlpha_whenUnlockedForOcclusion_ifPlayingOcclusionAnimation() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
+ }
+
+ @Test
+ public void keyguardAlpha_whenUnlockedForLaunch_ifLaunchingAffordance() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ when(mKeyguardViewMediator.isOccludeAnimationPlaying()).thenReturn(true);
+ mScrimController.setLaunchingAffordanceWithPreview(true);
+
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimAlpha(mNotificationsScrim, (int) (KEYGUARD_SCRIM_ALPHA * 255f));
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index fa7b2599c108..9957c2a7f4a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -14,8 +14,6 @@ import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.SysuiStatusBarStateController
import org.junit.Before
import org.junit.Test
@@ -40,7 +38,6 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
@Mock private lateinit var lightBarController: LightBarController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator
- @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var centralSurfaces: CentralSurfaces
private lateinit var sysBarAttrsListener: SystemBarAttributesListener
@@ -57,7 +54,6 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
sysBarAttrsListener =
SystemBarAttributesListener(
centralSurfaces,
- featureFlags,
letterboxAppearanceCalculator,
statusBarStateController,
lightBarController,
@@ -74,18 +70,14 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToCentralSurfaces() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
}
@Test
- fun onSysBarAttrsChanged_flagTrue_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>())
verify(centralSurfaces).setAppearance(TEST_APPEARANCE)
@@ -100,9 +92,7 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
verify(statusBarStateController)
@@ -120,9 +110,7 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
}
@Test
- fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToLightBarController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
-
+ fun onSysBarAttrsChanged_forwardsLetterboxAppearanceToLightBarController() {
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
verify(lightBarController)
@@ -135,7 +123,6 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToStatusBarStateController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -148,7 +135,6 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -164,7 +150,6 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
@Test
fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -175,7 +160,6 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
@Test
fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf())
reset(centralSurfaces, lightBarController, statusBarStateController)
@@ -184,17 +168,6 @@ class SystemBarAttributesListenerTest : SysuiTestCase() {
verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
}
- @Test
- fun onStatusBarBoundsChanged_flagFalse_doesNothing() {
- whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(false)
- changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
- reset(centralSurfaces, lightBarController, statusBarStateController)
-
- sysBarAttrsListener.onStatusBarBoundsChanged()
-
- verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
- }
-
private fun changeSysBarAttrs(@Appearance appearance: Int) {
changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>())
}
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
new file mode 100644
index 000000000000..6ff7b7ccd5e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -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.systemui.statusbar.pipeline.mobile.data.repository
+
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileConnectionRepository : MobileConnectionRepository {
+ private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
+ override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+
+ fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
+ _subscriptionsModelFlow.value = model
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index 0d1526883023..c88d468f1755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileSubscriptionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -18,11 +18,11 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.settingslib.mobile.MobileMappings.Config
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
+class FakeMobileConnectionsRepository : MobileConnectionsRepository {
private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
@@ -30,22 +30,27 @@ class FakeMobileSubscriptionRepository : MobileSubscriptionRepository {
MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
- private val subIdFlows = mutableMapOf<Int, MutableStateFlow<MobileSubscriptionModel>>()
- override fun getFlowForSubId(subId: Int): Flow<MobileSubscriptionModel> {
- return subIdFlows[subId]
- ?: MutableStateFlow(MobileSubscriptionModel()).also { subIdFlows[subId] = it }
+ private val _defaultDataSubRatConfig = MutableStateFlow(Config())
+ override val defaultDataSubRatConfig = _defaultDataSubRatConfig
+
+ private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
+ override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+ return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
}
fun setSubscriptions(subs: List<SubscriptionInfo>) {
_subscriptionsFlow.value = subs
}
+ fun setDefaultDataSubRatConfig(config: Config) {
+ _defaultDataSubRatConfig.value = config
+ }
+
fun setActiveMobileDataSubscriptionId(subId: Int) {
_activeMobileDataSubscriptionId.value = subId
}
- fun setMobileSubscriptionModel(model: MobileSubscriptionModel, subId: Int) {
- val subscription = subIdFlows[subId] ?: throw Exception("no flow exists for this subId yet")
- subscription.value = model
+ fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) {
+ subIdRepos[subId] = repo
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 316b795ac949..775e6dbb5e19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileSubscriptionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -22,18 +22,18 @@ import android.telephony.SignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
import android.telephony.TelephonyCallback
-import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
import android.telephony.TelephonyManager
+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.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.shared.ConnectivityPipelineLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
@@ -50,28 +50,32 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.verify
+import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-class MobileSubscriptionRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: MobileSubscriptionRepositoryImpl
+class MobileConnectionRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileConnectionRepositoryImpl
@Mock private lateinit var subscriptionManager: SubscriptionManager
@Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+
private val scope = CoroutineScope(IMMEDIATE)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
underTest =
- MobileSubscriptionRepositoryImpl(
- subscriptionManager,
+ MobileConnectionRepositoryImpl(
+ SUB_1_ID,
telephonyManager,
IMMEDIATE,
+ logger,
scope,
)
}
@@ -82,78 +86,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
}
@Test
- fun testSubscriptions_initiallyEmpty() =
- runBlocking(IMMEDIATE) {
- assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
- }
-
- @Test
- fun testSubscriptions_listUpdates() =
- runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
-
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
-
- job.cancel()
- }
-
- @Test
- fun testSubscriptions_removingSub_updatesList() =
- runBlocking(IMMEDIATE) {
- var latest: List<SubscriptionInfo>? = null
-
- val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
-
- // WHEN 2 networks show up
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // WHEN one network is removed
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // THEN the subscriptions list represents the newest change
- assertThat(latest).isEqualTo(listOf(SUB_2))
-
- job.cancel()
- }
-
- @Test
- fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
- runBlocking(IMMEDIATE) {
- assertThat(underTest.activeMobileDataSubscriptionId.value)
- .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
- }
-
- @Test
- fun testActiveDataSubscriptionId_updates() =
- runBlocking(IMMEDIATE) {
- var active: Int? = null
-
- val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
-
- getActiveDataSubscriptionCallback().onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
- assertThat(active).isEqualTo(SUB_2_ID)
-
- job.cancel()
- }
-
- @Test
fun testFlowForSubId_default() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
assertThat(latest).isEqualTo(MobileSubscriptionModel())
@@ -163,10 +99,8 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_emergencyOnly() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
@@ -181,10 +115,8 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_emergencyOnly_toggles() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState()
@@ -201,13 +133,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_signalStrengths_levelsUpdate() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
- val callback = getTelephonyCallbackForType<SignalStrengthsListener>()
- val strength = signalStrength(1, 2, true)
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
callback.onSignalStrengthsChanged(strength)
assertThat(latest?.isGsm).isEqualTo(true)
@@ -220,12 +150,11 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataConnectionState() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
- val callback = getTelephonyCallbackForType<DataConnectionStateListener>()
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(100, 200 /* unused */)
assertThat(latest?.dataConnectionState).isEqualTo(100)
@@ -236,12 +165,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_dataActivity() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
- val callback = getTelephonyCallbackForType<DataActivityListener>()
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
callback.onDataActivity(3)
assertThat(latest?.dataActivityDirection).isEqualTo(3)
@@ -252,12 +179,10 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
@Test
fun testFlowForSubId_carrierNetworkChange() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
- val callback = getTelephonyCallbackForType<CarrierNetworkListener>()
+ val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
callback.onCarrierNetworkChange(true)
assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
@@ -266,65 +191,59 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
}
@Test
- fun testFlowForSubId_displayInfo() =
+ fun subscriptionFlow_networkType_default() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
- val callback = getTelephonyCallbackForType<DisplayInfoListener>()
- val ti = mock<TelephonyDisplayInfo>()
- callback.onDisplayInfoChanged(ti)
+ val type = NETWORK_TYPE_UNKNOWN
+ val expected = DefaultNetworkType(type)
- assertThat(latest?.displayInfo).isEqualTo(ti)
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
job.cancel()
}
@Test
- fun testFlowForSubId_isCached() =
+ fun subscriptionFlow_networkType_updatesUsingDefault() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
- val state1 = underTest.getFlowForSubId(SUB_1_ID)
- val state2 = underTest.getFlowForSubId(SUB_1_ID)
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = NETWORK_TYPE_LTE
+ val expected = DefaultNetworkType(type)
+ val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ callback.onDisplayInfoChanged(ti)
- assertThat(state1).isEqualTo(state2)
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+
+ job.cancel()
}
@Test
- fun testFlowForSubId_isRemovedAfterFinish() =
+ fun subscriptionFlow_networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) {
- whenever(telephonyManager.createForSubscriptionId(any())).thenReturn(telephonyManager)
-
var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = OVERRIDE_NETWORK_TYPE_LTE_CA
+ val expected = OverrideNetworkType(type)
+ val ti =
+ mock<TelephonyDisplayInfo>().also {
+ whenever(it.overrideNetworkType).thenReturn(type)
+ }
+ callback.onDisplayInfoChanged(ti)
- // Start collecting on some flow
- val job = underTest.getFlowForSubId(SUB_1_ID).onEach { latest = it }.launchIn(this)
-
- // There should be once cached flow now
- assertThat(underTest.getSubIdFlowCache().size).isEqualTo(1)
+ assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
- // When the job is canceled, the cache should be cleared
job.cancel()
-
- assertThat(underTest.getSubIdFlowCache().size).isEqualTo(0)
}
- private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
- val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
- verify(subscriptionManager)
- .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
- return callbackCaptor.value!!
- }
-
- private fun getActiveDataSubscriptionCallback(): ActiveDataSubscriptionIdListener =
- getTelephonyCallbackForType()
-
private fun getTelephonyCallbacks(): List<TelephonyCallback> {
val callbackCaptor = argumentCaptor<TelephonyCallback>()
- verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
return callbackCaptor.allValues
}
@@ -352,9 +271,5 @@ class MobileSubscriptionRepositoryTest : SysuiTestCase() {
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
-
- private const val SUB_2_ID = 2
- private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
}
}
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/MobileConnectionsRepositoryTest.kt
new file mode 100644
index 000000000000..326e0d28166f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -0,0 +1,246 @@
+/*
+ * 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.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+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.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionsRepositoryTest : SysuiTestCase() {
+ private lateinit var underTest: MobileConnectionsRepositoryImpl
+
+ @Mock private lateinit var subscriptionManager: SubscriptionManager
+ @Mock private lateinit var telephonyManager: TelephonyManager
+ @Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ private val scope = CoroutineScope(IMMEDIATE)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ ArgumentMatchers.anyInt(),
+ nullable(),
+ )
+ )
+ .thenReturn(flowOf(Unit))
+
+ underTest =
+ MobileConnectionsRepositoryImpl(
+ subscriptionManager,
+ telephonyManager,
+ logger,
+ broadcastDispatcher,
+ context,
+ IMMEDIATE,
+ scope,
+ mock(),
+ )
+ }
+
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_initiallyEmpty() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+ }
+
+ @Test
+ fun testSubscriptions_listUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testSubscriptions_removingSub_updatesList() =
+ runBlocking(IMMEDIATE) {
+ var latest: List<SubscriptionInfo>? = null
+
+ val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+
+ // WHEN 2 networks show up
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN one network is removed
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // THEN the subscriptions list represents the newest change
+ assertThat(latest).isEqualTo(listOf(SUB_2))
+
+ job.cancel()
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_initialValueIsInvalidId() =
+ runBlocking(IMMEDIATE) {
+ assertThat(underTest.activeMobileDataSubscriptionId.value)
+ .isEqualTo(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ }
+
+ @Test
+ fun testActiveDataSubscriptionId_updates() =
+ runBlocking(IMMEDIATE) {
+ var active: Int? = null
+
+ val job = underTest.activeMobileDataSubscriptionId.onEach { active = it }.launchIn(this)
+
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(active).isEqualTo(SUB_2_ID)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionRepository_validSubId_isCached() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1).isSameInstanceAs(repo2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionCache_clearsInvalidSubscriptions() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.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)
+
+ // SUB_2 disappears
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun testConnectionRepository_invalidSubId_throws() =
+ runBlocking(IMMEDIATE) {
+ val job = underTest.subscriptionsFlow.launchIn(this)
+
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.getRepoForSubId(SUB_1_ID)
+ }
+
+ job.cancel()
+ }
+
+ private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager)
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.value!!
+ }
+
+ private fun getTelephonyCallbacks(): List<TelephonyCallback> {
+ val callbackCaptor = argumentCaptor<TelephonyCallback>()
+ verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ private inline fun <reified T> getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacks().filterIsInstance<T>()
+ assertThat(cbs.size).isEqualTo(1)
+ return cbs[0]
+ }
+
+ 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 const val SUB_2_ID = 2
+ private val SUB_2 =
+ mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 8ec68f36a837..cd4dbebcc35c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -23,7 +23,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
class FakeMobileIconInteractor : MobileIconInteractor {
private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
- override val iconGroup = _iconGroup
+ override val networkTypeIconGroup = _iconGroup
private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
override val isEmergencyOnly = _isEmergencyOnly
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
new file mode 100644
index 000000000000..2bd228603cb0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -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 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.util.MobileMappingsProxy
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
+ MobileIconsInteractor {
+ val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
+ val LTE_KEY = mobileMappings.toIconKey(LTE)
+ val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
+ val FIVE_G_OVERRIDE_KEY = mobileMappings.toIconKeyOverride(FIVE_G_OVERRIDE)
+
+ /**
+ * 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, MobileIconGroup> =
+ mapOf(
+ THREE_G_KEY to TelephonyIcons.THREE_G,
+ LTE_KEY to TelephonyIcons.LTE,
+ FOUR_G_KEY to TelephonyIcons.FOUR_G,
+ FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
+ )
+
+ private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
+ override val filteredSubscriptions = _filteredSubscriptions
+
+ private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
+ override val defaultMobileIconMapping = _defaultMobileIconMapping
+
+ private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
+ override val defaultMobileIconGroup = _defaultMobileIconGroup
+
+ private val _isUserSetup = MutableStateFlow(true)
+ override val isUserSetup = _isUserSetup
+
+ /** Always returns a new fake interactor */
+ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
+ return FakeMobileIconInteractor()
+ }
+
+ companion object {
+ val DEFAULT_ICON = TelephonyIcons.G
+
+ // Use [MobileMappings] to define some simple definitions
+ const val THREE_G = NETWORK_TYPE_GSM
+ const val LTE = NETWORK_TYPE_LTE
+ const val FOUR_G = NETWORK_TYPE_UMTS
+ const val FIVE_G_OVERRIDE = OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+ }
+}
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 2f07d9cb3831..ff44af4c9204 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
@@ -18,10 +18,19 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.CellSignalStrength
import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
+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.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.model.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
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.THREE_G
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -29,26 +38,33 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.Before
import org.junit.Test
@SmallTest
class MobileIconInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconInteractor
- private val mobileSubscriptionRepository = FakeMobileSubscriptionRepository()
- private val sub1Flow = mobileSubscriptionRepository.getFlowForSubId(SUB_1_ID)
+ private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
+ private val connectionRepository = FakeMobileConnectionRepository()
@Before
fun setUp() {
- underTest = MobileIconInteractorImpl(sub1Flow)
+ underTest =
+ MobileIconInteractorImpl(
+ mobileIconsInteractor.defaultMobileIconMapping,
+ mobileIconsInteractor.defaultMobileIconGroup,
+ mobileMappingsProxy,
+ connectionRepository,
+ )
}
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = true),
- SUB_1_ID
)
var latest: Int? = null
@@ -62,13 +78,12 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = true,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
- SUB_1_ID
)
var latest: Int? = null
@@ -82,9 +97,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun cdma_level_default_unknown() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(isGsm = false),
- SUB_1_ID
)
var latest: Int? = null
@@ -97,13 +111,12 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun cdma_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
- mobileSubscriptionRepository.setMobileSubscriptionModel(
+ connectionRepository.setMobileSubscriptionModel(
MobileSubscriptionModel(
isGsm = false,
primaryLevel = GSM_LEVEL,
cdmaLevel = CDMA_LEVEL
),
- SUB_1_ID
)
var latest: Int? = null
@@ -114,6 +127,75 @@ class MobileIconInteractorTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun iconGroup_three_g() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(TelephonyIcons.THREE_G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_updates_on_change() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ resolvedNetworkType = DefaultNetworkType(FOUR_G),
+ ),
+ )
+ yield()
+
+ assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_5g_override_type() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(TelephonyIcons.NR_5G)
+
+ job.cancel()
+ }
+
+ @Test
+ fun iconGroup_default_if_no_lookup() =
+ runBlocking(IMMEDIATE) {
+ connectionRepository.setMobileSubscriptionModel(
+ MobileSubscriptionModel(
+ resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
+ ),
+ )
+
+ var latest: MobileIconGroup? = null
+ val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isEqualTo(FakeMobileIconsInteractor.DEFAULT_ICON)
+
+ job.cancel()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
@@ -123,9 +205,5 @@ class MobileIconInteractorTest : SysuiTestCase() {
private const val SUB_1_ID = 1
private val SUB_1 =
mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
-
- private const val SUB_2_ID = 2
- private val SUB_2 =
- mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
}
}
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 89ad9cb9e51e..b01efd18971f 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
@@ -19,12 +19,14 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
import android.telephony.SubscriptionInfo
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileSubscriptionRepository
+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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -39,7 +41,9 @@ import org.mockito.MockitoAnnotations
class MobileIconsInteractorTest : SysuiTestCase() {
private lateinit var underTest: MobileIconsInteractor
private val userSetupRepository = FakeUserSetupRepository()
- private val subscriptionsRepository = FakeMobileSubscriptionRepository()
+ private val subscriptionsRepository = FakeMobileConnectionsRepository()
+ private val mobileMappingsProxy = FakeMobileMappingsProxy()
+ private val scope = CoroutineScope(IMMEDIATE)
@Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -47,10 +51,12 @@ class MobileIconsInteractorTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest =
- MobileIconsInteractor(
+ MobileIconsInteractorImpl(
subscriptionsRepository,
carrierConfigTracker,
+ mobileMappingsProxy,
userSetupRepository,
+ scope
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
new file mode 100644
index 000000000000..6d8d902615de
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.util
+
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.settingslib.mobile.TelephonyIcons
+
+class FakeMobileMappingsProxy : MobileMappingsProxy {
+ private var iconMap = mapOf<String, MobileIconGroup>()
+ private var defaultIcons = TelephonyIcons.THREE_G
+
+ fun setIconMap(map: Map<String, MobileIconGroup>) {
+ iconMap = map
+ }
+ override fun mapIconSets(config: Config): Map<String, MobileIconGroup> = iconMap
+ fun getIconMap() = iconMap
+
+ fun setDefaultIcons(group: MobileIconGroup) {
+ defaultIcons = group
+ }
+ override fun getDefaultIcons(config: Config): MobileIconGroup = defaultIcons
+ fun getDefaultIcons(): MobileIconGroup = defaultIcons
+
+ override fun toIconKey(networkType: Int): String {
+ return networkType.toString()
+ }
+
+ override fun toIconKeyOverride(networkType: Int): String {
+ return toIconKey(networkType) + "_override"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index b68eb88d46db..91b5c35d9661 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -41,6 +41,7 @@ import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -85,10 +86,29 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
- fun displayView_viewAdded() {
- underTest.displayView(getState())
+ fun displayView_viewAddedWithCorrectTitle() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
+ )
+ )
- verify(windowManager).addView(any(), any())
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+ verify(windowManager).addView(any(), capture(windowParamsCaptor))
+ assertThat(windowParamsCaptor.value!!.title).isEqualTo("Fake Window Title")
+ }
+
+ @Test
+ fun displayView_logged() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Fake Window Title",
+ )
+ )
+
+ verify(logger).logViewAddition("Fake Window Title")
}
@Test
@@ -110,7 +130,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
- fun displayView_twice_viewNotAddedTwice() {
+ fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
underTest.displayView(getState())
reset(windowManager)
@@ -119,6 +139,32 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
@Test
+ fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "First Fake Window Title",
+ )
+ )
+
+ underTest.displayView(
+ ViewInfo(
+ name = "name",
+ windowTitle = "Second Fake Window Title",
+ )
+ )
+
+ val viewCaptor = argumentCaptor<View>()
+ val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+
+ verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+
+ assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
+ assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
+ verify(windowManager).removeView(viewCaptor.allValues[0])
+ }
+
+ @Test
fun displayView_viewDoesNotDisappearsBeforeTimeout() {
val state = getState()
underTest.displayView(state)
@@ -197,7 +243,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
underTest.removeView(reason)
verify(windowManager).removeView(any())
- verify(logger).logChipRemoval(reason)
+ verify(logger).logViewRemoval(reason)
}
@Test
@@ -232,8 +278,6 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
configurationController,
powerManager,
R.layout.chipbar,
- "Window Title",
- "WAKE_REASON",
) {
var mostRecentViewInfo: ViewInfo? = null
@@ -250,9 +294,12 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
}
}
- inner class ViewInfo(val name: String) : TemporaryViewInfo {
- override fun getTimeoutMs() = 1L
- }
+ inner class ViewInfo(
+ val name: String,
+ override val windowTitle: String = "Window Title",
+ override val wakeReason: String = "WAKE_REASON",
+ override val timeoutMs: Int = 1
+ ) : TemporaryViewInfo()
}
private const val TIMEOUT_MS = 10000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 13e9f608158e..d155050ce932 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -43,20 +43,21 @@ class TemporaryViewLoggerTest : SysuiTestCase() {
}
@Test
- fun logChipAddition_bufferHasLog() {
- logger.logChipAddition()
+ fun logViewAddition_bufferHasLog() {
+ logger.logViewAddition("Test Window Title")
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
val actualString = stringWriter.toString()
assertThat(actualString).contains(TAG)
+ assertThat(actualString).contains("Test Window Title")
}
@Test
- fun logChipRemoval_bufferHasTagAndReason() {
+ fun logViewRemoval_bufferHasTagAndReason() {
val reason = "test reason"
- logger.logChipRemoval(reason)
+ logger.logViewRemoval(reason)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
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 9fbf159ec348..f64397325867 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,12 +35,12 @@ 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.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -60,7 +60,7 @@ import org.mockito.MockitoAnnotations
class ChipbarCoordinatorTest : SysuiTestCase() {
private lateinit var underTest: FakeChipbarCoordinator
- @Mock private lateinit var logger: MediaTttLogger
+ @Mock private lateinit var logger: ChipbarLogger
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var powerManager: PowerManager
@@ -105,7 +105,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("text"),
endItem = null,
@@ -121,7 +121,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
fun displayView_resourceIcon_correctlyRendered() {
val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.drawable.ic_cake, contentDescription),
Text.Loaded("text"),
endItem = null,
@@ -136,7 +136,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_loadedText_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("display view text here"),
endItem = null,
@@ -149,7 +149,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_resourceText_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Resource(R.string.screenrecord_start_error),
endItem = null,
@@ -163,7 +163,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemNull_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -179,7 +179,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemLoading_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Loading,
@@ -195,7 +195,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemError_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = ChipbarEndItem.Error,
@@ -211,7 +211,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_endItemButton_correctlyRendered() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -237,7 +237,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -260,7 +260,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val buttonClickListener = View.OnClickListener { isClicked = true }
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem =
@@ -279,7 +279,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Test
fun displayView_vibrationEffect_doubleClickEffect() {
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Resource(R.id.check_box, null),
Text.Loaded("text"),
endItem = null,
@@ -296,7 +296,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
val drawable = context.getDrawable(R.drawable.ic_celebration)!!
underTest.displayView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
Text.Loaded("title text"),
endItem = ChipbarEndItem.Loading,
@@ -314,7 +314,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
// WHEN the view is updated
val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
underTest.updateView(
- ChipbarInfo(
+ createChipbarInfo(
Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
Text.Loaded("new title text"),
endItem = ChipbarEndItem.Error,
@@ -331,6 +331,47 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
}
+ @Test
+ fun viewUpdates_logged() {
+ val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+ Text.Loaded("title text"),
+ endItem = ChipbarEndItem.Loading,
+ )
+ )
+
+ verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("title text"), any())
+
+ underTest.displayView(
+ createChipbarInfo(
+ Icon.Loaded(drawable, ContentDescription.Loaded("new CD")),
+ Text.Loaded("new title text"),
+ endItem = ChipbarEndItem.Error,
+ )
+ )
+
+ verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
+ }
+
+ private fun createChipbarInfo(
+ startIcon: Icon,
+ text: Text,
+ endItem: ChipbarEndItem?,
+ vibrationEffect: VibrationEffect? = null,
+ ): ChipbarInfo {
+ return ChipbarInfo(
+ startIcon,
+ text,
+ endItem,
+ vibrationEffect,
+ windowTitle = WINDOW_TITLE,
+ wakeReason = WAKE_REASON,
+ timeoutMs = TIMEOUT,
+ )
+ }
+
private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
private fun ViewGroup.getChipText(): String =
@@ -350,3 +391,5 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
}
private const val TIMEOUT = 10000
+private const val WINDOW_TITLE = "Test Chipbar Window Title"
+private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 17d402319246..574f70e7fddc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -22,8 +22,6 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -33,7 +31,7 @@ import com.android.systemui.util.view.ViewUtil
/** A fake implementation of [ChipbarCoordinator] for testing. */
class FakeChipbarCoordinator(
context: Context,
- @MediaTttReceiverLogger logger: MediaTttLogger,
+ logger: ChipbarLogger,
windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index e18dd3a3c846..7d5f06c890c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -140,6 +140,40 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
@Test
+ fun testOnUnfold_hingeAngleDecreasesBeforeInnerScreenAvailable_emitsOnlyStartAndInnerScreenAvailableEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ sendHingeAngleEvent(10)
+ sendHingeAngleEvent(20)
+ sendHingeAngleEvent(10)
+ screenOnStatusProvider.notifyScreenTurnedOn()
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE)
+ }
+
+ @Test
+ fun testOnUnfold_hingeAngleDecreasesAfterInnerScreenAvailable_emitsStartInnerScreenAvailableAndStartClosingEvents() {
+ setFoldState(folded = true)
+ foldUpdates.clear()
+
+ setFoldState(folded = false)
+ screenOnStatusProvider.notifyScreenTurningOn()
+ sendHingeAngleEvent(10)
+ sendHingeAngleEvent(20)
+ screenOnStatusProvider.notifyScreenTurnedOn()
+ sendHingeAngleEvent(30)
+ sendHingeAngleEvent(40)
+ sendHingeAngleEvent(10)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_OPENING,
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE, FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
fun testOnFolded_stopsHingeAngleProvider() {
setFoldState(folded = true)
@@ -237,7 +271,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
@Test
- fun startClosingEvent_afterTimeout_abortEmitted() {
+ fun startClosingEvent_afterTimeout_finishHalfOpenEventEmitted() {
sendHingeAngleEvent(90)
sendHingeAngleEvent(80)
@@ -269,7 +303,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
@Test
- fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
+ fun startClosingEvent_timeoutAfterTimeoutRescheduled_finishHalfOpenStateEmitted() {
sendHingeAngleEvent(180)
sendHingeAngleEvent(90)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
index 76bff1d72141..7e8ffeb7f9e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperColorExtractorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/canvas/WallpaperLocalColorExtractorTest.java
@@ -54,7 +54,7 @@ import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class WallpaperColorExtractorTest extends SysuiTestCase {
+public class WallpaperLocalColorExtractorTest extends SysuiTestCase {
private static final int LOW_BMP_WIDTH = 128;
private static final int LOW_BMP_HEIGHT = 128;
private static final int HIGH_BMP_WIDTH = 3000;
@@ -105,11 +105,11 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
return bitmap;
}
- private WallpaperColorExtractor getSpyWallpaperColorExtractor() {
+ private WallpaperLocalColorExtractor getSpyWallpaperLocalColorExtractor() {
- WallpaperColorExtractor wallpaperColorExtractor = new WallpaperColorExtractor(
+ WallpaperLocalColorExtractor colorExtractor = new WallpaperLocalColorExtractor(
mBackgroundExecutor,
- new WallpaperColorExtractor.WallpaperColorExtractorCallback() {
+ new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
public void onColorsProcessed(List<RectF> regions,
List<WallpaperColors> colors) {
@@ -132,25 +132,25 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
mDeactivatedCount++;
}
});
- WallpaperColorExtractor spyWallpaperColorExtractor = spy(wallpaperColorExtractor);
+ WallpaperLocalColorExtractor spyColorExtractor = spy(colorExtractor);
doAnswer(invocation -> {
mMiniBitmapWidth = invocation.getArgument(1);
mMiniBitmapHeight = invocation.getArgument(2);
return getMockBitmap(mMiniBitmapWidth, mMiniBitmapHeight);
- }).when(spyWallpaperColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
+ }).when(spyColorExtractor).createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
doAnswer(invocation -> getMockBitmap(
invocation.getArgument(1),
invocation.getArgument(2)))
- .when(spyWallpaperColorExtractor)
+ .when(spyColorExtractor)
.createMiniBitmap(any(Bitmap.class), anyInt(), anyInt());
doReturn(new WallpaperColors(Color.valueOf(0), Color.valueOf(0), Color.valueOf(0)))
- .when(spyWallpaperColorExtractor).getLocalWallpaperColors(any(Rect.class));
+ .when(spyColorExtractor).getLocalWallpaperColors(any(Rect.class));
- return spyWallpaperColorExtractor;
+ return spyColorExtractor;
}
private RectF randomArea() {
@@ -180,18 +180,18 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
*/
@Test
public void testMiniBitmapCreation() {
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
int width = randomBetween(LOW_BMP_WIDTH, HIGH_BMP_WIDTH);
int height = randomBetween(LOW_BMP_HEIGHT, HIGH_BMP_HEIGHT);
Bitmap bitmap = getMockBitmap(width, height);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(Math.min(mMiniBitmapWidth, mMiniBitmapHeight))
- .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ .isAtMost(WallpaperLocalColorExtractor.SMALL_SIDE);
}
}
@@ -201,18 +201,18 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
*/
@Test
public void testSmallMiniBitmapCreation() {
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
int width = randomBetween(VERY_LOW_BMP_WIDTH, LOW_BMP_WIDTH);
int height = randomBetween(VERY_LOW_BMP_HEIGHT, LOW_BMP_HEIGHT);
Bitmap bitmap = getMockBitmap(width, height);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(Math.max(mMiniBitmapWidth, mMiniBitmapHeight))
- .isAtMost(WallpaperColorExtractor.SMALL_SIDE);
+ .isAtMost(WallpaperLocalColorExtractor.SMALL_SIDE);
}
}
@@ -228,15 +228,15 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions = listOfRandomAreas(MIN_AREAS, MAX_AREAS);
int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
List<Runnable> tasks = Arrays.asList(
- () -> spyWallpaperColorExtractor.onPageChanged(nPages),
- () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
- () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ () -> spyColorExtractor.onPageChanged(nPages),
+ () -> spyColorExtractor.onBitmapChanged(bitmap),
+ () -> spyColorExtractor.setDisplayDimensions(
DISPLAY_WIDTH, DISPLAY_HEIGHT),
- () -> spyWallpaperColorExtractor.addLocalColorsAreas(
+ () -> spyColorExtractor.addLocalColorsAreas(
regions));
Collections.shuffle(tasks);
tasks.forEach(Runnable::run);
@@ -245,7 +245,7 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(mColorsProcessed).isEqualTo(regions.size());
- spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
}
@@ -260,7 +260,7 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
int nSimulations = 10;
for (int i = 0; i < nSimulations; i++) {
resetCounters();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions = new ArrayList<>();
@@ -268,20 +268,20 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
regions.addAll(regions2);
int nPages = randomBetween(PAGES_LOW, PAGES_HIGH);
List<Runnable> tasks = Arrays.asList(
- () -> spyWallpaperColorExtractor.onPageChanged(nPages),
- () -> spyWallpaperColorExtractor.onBitmapChanged(bitmap),
- () -> spyWallpaperColorExtractor.setDisplayDimensions(
+ () -> spyColorExtractor.onPageChanged(nPages),
+ () -> spyColorExtractor.onBitmapChanged(bitmap),
+ () -> spyColorExtractor.setDisplayDimensions(
DISPLAY_WIDTH, DISPLAY_HEIGHT),
- () -> spyWallpaperColorExtractor.removeLocalColorAreas(regions1));
+ () -> spyColorExtractor.removeLocalColorAreas(regions1));
- spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ spyColorExtractor.addLocalColorsAreas(regions);
assertThat(mActivatedCount).isEqualTo(1);
Collections.shuffle(tasks);
tasks.forEach(Runnable::run);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
assertThat(mDeactivatedCount).isEqualTo(0);
- spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ spyColorExtractor.removeLocalColorAreas(regions2);
assertThat(mDeactivatedCount).isEqualTo(1);
}
}
@@ -295,18 +295,18 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
@Test
public void testRecomputeColorExtraction() {
Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
List<RectF> regions1 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions2 = listOfRandomAreas(MIN_AREAS / 2, MAX_AREAS / 2);
List<RectF> regions = new ArrayList<>();
regions.addAll(regions1);
regions.addAll(regions2);
- spyWallpaperColorExtractor.addLocalColorsAreas(regions);
+ spyColorExtractor.addLocalColorsAreas(regions);
assertThat(mActivatedCount).isEqualTo(1);
int nPages = PAGES_LOW;
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
- spyWallpaperColorExtractor.onPageChanged(nPages);
- spyWallpaperColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
+ spyColorExtractor.onBitmapChanged(bitmap);
+ spyColorExtractor.onPageChanged(nPages);
+ spyColorExtractor.setDisplayDimensions(DISPLAY_WIDTH, DISPLAY_HEIGHT);
int nSimulations = 20;
for (int i = 0; i < nSimulations; i++) {
@@ -315,22 +315,22 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
// verify that if we remove some regions, they are not recomputed after other changes
if (i == nSimulations / 2) {
regions.removeAll(regions2);
- spyWallpaperColorExtractor.removeLocalColorAreas(regions2);
+ spyColorExtractor.removeLocalColorAreas(regions2);
}
if (Math.random() >= 0.5) {
int nPagesNew = randomBetween(PAGES_LOW, PAGES_HIGH);
if (nPagesNew == nPages) continue;
nPages = nPagesNew;
- spyWallpaperColorExtractor.onPageChanged(nPagesNew);
+ spyColorExtractor.onPageChanged(nPagesNew);
} else {
Bitmap newBitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
- spyWallpaperColorExtractor.onBitmapChanged(newBitmap);
+ spyColorExtractor.onBitmapChanged(newBitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
}
assertThat(mColorsProcessed).isEqualTo(regions.size());
}
- spyWallpaperColorExtractor.removeLocalColorAreas(regions);
+ spyColorExtractor.removeLocalColorAreas(regions);
assertThat(mDeactivatedCount).isEqualTo(1);
}
@@ -339,12 +339,12 @@ public class WallpaperColorExtractorTest extends SysuiTestCase {
resetCounters();
Bitmap bitmap = getMockBitmap(HIGH_BMP_WIDTH, HIGH_BMP_HEIGHT);
doNothing().when(bitmap).recycle();
- WallpaperColorExtractor spyWallpaperColorExtractor = getSpyWallpaperColorExtractor();
- spyWallpaperColorExtractor.onPageChanged(PAGES_LOW);
- spyWallpaperColorExtractor.onBitmapChanged(bitmap);
+ WallpaperLocalColorExtractor spyColorExtractor = getSpyWallpaperLocalColorExtractor();
+ spyColorExtractor.onPageChanged(PAGES_LOW);
+ spyColorExtractor.onBitmapChanged(bitmap);
assertThat(mMiniBitmapUpdatedCount).isEqualTo(1);
- spyWallpaperColorExtractor.cleanUp();
- spyWallpaperColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
+ spyColorExtractor.cleanUp();
+ spyColorExtractor.addLocalColorsAreas(listOfRandomAreas(MIN_AREAS, MAX_AREAS));
assertThat(mColorsProcessed).isEqualTo(0);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 7af66f641837..7ae47b41d5ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -28,6 +28,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.notetask.NoteTaskInitializer;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -36,7 +37,6 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
-import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -78,18 +78,31 @@ public class WMShellTest extends SysuiTestCase {
@Mock ProtoTracer mProtoTracer;
@Mock UserTracker mUserTracker;
@Mock ShellExecutor mSysUiMainExecutor;
- @Mock FloatingTasks mFloatingTasks;
+ @Mock NoteTaskInitializer mNoteTaskInitializer;
@Mock DesktopMode mDesktopMode;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
- Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks),
+ mWMShell = new WMShell(
+ mContext,
+ mShellInterface,
+ Optional.of(mPip),
+ Optional.of(mSplitScreen),
+ Optional.of(mOneHanded),
Optional.of(mDesktopMode),
- mCommandQueue, mConfigurationController, mKeyguardStateController,
- mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer,
- mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor);
+ mCommandQueue,
+ mConfigurationController,
+ mKeyguardStateController,
+ mKeyguardUpdateMonitor,
+ mScreenLifecycle,
+ mSysUiState,
+ mProtoTracer,
+ mWakefulnessLifecycle,
+ mUserTracker,
+ mNoteTaskInitializer,
+ mSysUiMainExecutor
+ );
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
new file mode 100644
index 000000000000..96658c61109d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -0,0 +1,48 @@
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.data.model.PromptKind
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [PromptRepository] for tests. */
+class FakePromptRepository : PromptRepository {
+
+ private val _isShowing = MutableStateFlow(false)
+ override val isShowing = _isShowing.asStateFlow()
+
+ private val _promptInfo = MutableStateFlow<PromptInfo?>(null)
+ override val promptInfo = _promptInfo.asStateFlow()
+
+ private val _userId = MutableStateFlow<Int?>(null)
+ override val userId = _userId.asStateFlow()
+
+ private var _challenge = MutableStateFlow<Long?>(null)
+ override val challenge = _challenge.asStateFlow()
+
+ private val _kind = MutableStateFlow(PromptKind.ANY_BIOMETRIC)
+ override val kind = _kind.asStateFlow()
+
+ override fun setPrompt(
+ promptInfo: PromptInfo,
+ userId: Int,
+ gatekeeperChallenge: Long?,
+ kind: PromptKind
+ ) {
+ _promptInfo.value = promptInfo
+ _userId.value = userId
+ _challenge.value = gatekeeperChallenge
+ _kind.value = kind
+ }
+
+ override fun unsetPrompt() {
+ _promptInfo.value = null
+ _userId.value = null
+ _challenge.value = null
+ _kind.value = PromptKind.ANY_BIOMETRIC
+ }
+
+ fun setIsShowing(showing: Boolean) {
+ _isShowing.value = showing
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
new file mode 100644
index 000000000000..fbe291ebaf5d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/FakeCredentialInteractor.kt
@@ -0,0 +1,31 @@
+package com.android.systemui.biometrics.domain.interactor
+
+import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/** Fake implementation of [CredentialInteractor] for tests. */
+class FakeCredentialInteractor : CredentialInteractor {
+
+ /** Sets return value for [isStealthModeActive]. */
+ var stealthMode: Boolean = false
+
+ /** Sets return value for [getCredentialOwnerOrSelfId]. */
+ var credentialOwnerId: Int? = null
+
+ override fun isStealthModeActive(userId: Int): Boolean = stealthMode
+
+ override fun getCredentialOwnerOrSelfId(userId: Int): Int = credentialOwnerId ?: userId
+
+ override fun verifyCredential(
+ request: BiometricPromptRequest.Credential,
+ credential: LockscreenCredential,
+ ): Flow<CredentialStatus> = verifyCredentialResponse(credential)
+
+ /** Sets the result value for [verifyCredential]. */
+ var verifyCredentialResponse: (credential: LockscreenCredential) -> Flow<CredentialStatus> =
+ { _ ->
+ flowOf(CredentialStatus.Fail.Error("invalid"))
+ }
+}
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 043aff659d6c..b56818693124 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
@@ -15,6 +15,7 @@
*/
package com.android.systemui.unfold.progress
+import android.os.Trace
import android.util.Log
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -117,6 +118,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider(
if (DEBUG) {
Log.d(TAG, "onFoldUpdate = $update")
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "fold_update", update)
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 07473b30dd58..808128d16b7e 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -16,6 +16,7 @@
package com.android.systemui.unfold.updates
import android.os.Handler
+import android.os.Trace
import android.util.Log
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
@@ -108,6 +109,7 @@ constructor(
private fun onHingeAngle(angle: Float) {
if (DEBUG) {
Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle")
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "hinge_angle", angle.toInt())
}
val isClosing = angle < lastHingeAngle
@@ -115,8 +117,16 @@ constructor(
val closingThresholdMet = closingThreshold == null || angle < closingThreshold
val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
-
- if (isClosing && closingThresholdMet && !closingEventDispatched && !isFullyOpened) {
+ val screenAvailableEventSent = isUnfoldHandled
+
+ if (isClosing // hinge angle should be decreasing since last update
+ && closingThresholdMet // hinge angle is below certain threshold
+ && !closingEventDispatched // we haven't sent closing event already
+ && !isFullyOpened // do not send closing event if we are in fully opened hinge
+ // angle range as closing threshold could overlap this range
+ && screenAvailableEventSent // do not send closing event if we are still in
+ // the process of turning on the inner display
+ ) {
notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 3d24588c9763..9897a0762d9f 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -19,7 +19,6 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.graphics.Camera;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -124,26 +123,31 @@ public class CameraExtensionsProxyService extends Service {
private static final String CAMERA_EXTENSION_VERSION_NAME =
"androidx.camera.extensions.impl.ExtensionVersionImpl";
- private static final String LATEST_VERSION = "1.3.0";
+ private static final String LATEST_VERSION = "1.4.0";
// No support for the init sequence
private static final String NON_INIT_VERSION_PREFIX = "1.0";
// Support advanced API and latency queries
private static final String ADVANCED_VERSION_PREFIX = "1.2";
// Support for the capture request & result APIs
private static final String RESULTS_VERSION_PREFIX = "1.3";
- private static final String[] ADVANCED_VERSION_PREFIXES = {ADVANCED_VERSION_PREFIX,
- RESULTS_VERSION_PREFIX};
- private static final String[] SUPPORTED_VERSION_PREFIXES = {RESULTS_VERSION_PREFIX,
- ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX};
+ // Support for various latency improvements
+ private static final String LATENCY_VERSION_PREFIX = "1.4";
+ private static final String[] ADVANCED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
+ ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
+ private static final String[] SUPPORTED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
+ RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX};
private static final boolean EXTENSIONS_PRESENT = checkForExtensions();
private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ?
(new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null;
private static final boolean LATENCY_API_SUPPORTED = checkForLatencyAPI();
+ private static final boolean LATENCY_IMPROVEMENTS_SUPPORTED = EXTENSIONS_PRESENT &&
+ (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI();
private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT &&
(!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX));
private static final boolean RESULT_API_SUPPORTED = EXTENSIONS_PRESENT &&
- (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX));
+ (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX) ||
+ EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
private HashMap<String, CameraCharacteristics> mCharacteristicsHashMap = new HashMap<>();
private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
@@ -1169,6 +1173,10 @@ public class CameraExtensionsProxyService extends Service {
ret.outputConfigs.add(entry);
}
ret.sessionTemplateId = sessionConfig.getSessionTemplateId();
+ ret.sessionType = -1;
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ ret.sessionType = sessionConfig.getSessionType();
+ }
ret.sessionParameter = initializeParcelableMetadata(
sessionConfig.getSessionParameters(), cameraId);
mCameraId = cameraId;
@@ -1312,6 +1320,15 @@ public class CameraExtensionsProxyService extends Service {
}
@Override
+ public int getSessionType() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ return mPreviewExtender.onSessionType();
+ }
+
+ return -1;
+ }
+
+ @Override
public int getProcessorType() {
ProcessorType processorType = mPreviewExtender.getProcessorType();
if (processorType == ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY) {
@@ -1407,6 +1424,15 @@ public class CameraExtensionsProxyService extends Service {
}
@Override
+ public int getSessionType() {
+ if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+ return mImageExtender.onSessionType();
+ }
+
+ return -1;
+ }
+
+ @Override
public void init(String cameraId, CameraMetadataNative chars) {
CameraCharacteristics c = new CameraCharacteristics(chars);
mCameraManager.registerDeviceStateListener(c);
diff --git a/proto/src/task_snapshot.proto b/proto/src/task_snapshot.proto
deleted file mode 100644
index 1cbc17ed9f41..000000000000
--- a/proto/src/task_snapshot.proto
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- syntax = "proto3";
-
- package com.android.server.wm;
-
- option java_package = "com.android.server.wm";
- option java_outer_classname = "WindowManagerProtos";
-
- message TaskSnapshotProto {
- int32 orientation = 1;
- int32 inset_left = 2;
- int32 inset_top = 3;
- int32 inset_right = 4;
- int32 inset_bottom = 5;
- bool is_real_snapshot = 6;
- int32 windowing_mode = 7;
- int32 system_ui_visibility = 8 [deprecated=true];
- bool is_translucent = 9;
- string top_activity_component = 10;
- // deprecated because original width and height are stored now instead of the scale.
- float legacy_scale = 11 [deprecated=true];
- int64 id = 12;
- int32 rotation = 13;
- // The task width when the snapshot was taken
- int32 task_width = 14;
- // The task height when the snapshot was taken
- int32 task_height = 15;
- int32 appearance = 16;
- int32 letterbox_inset_left = 17;
- int32 letterbox_inset_top = 18;
- int32 letterbox_inset_right = 19;
- int32 letterbox_inset_bottom = 20;
- }
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
new file mode 100644
index 000000000000..f26404c66623
--- /dev/null
+++ b/proto/src/windowmanager.proto
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+package com.android.server.wm;
+
+option java_package = "com.android.server.wm";
+option java_outer_classname = "WindowManagerProtos";
+
+message TaskSnapshotProto {
+ int32 orientation = 1;
+ int32 inset_left = 2;
+ int32 inset_top = 3;
+ int32 inset_right = 4;
+ int32 inset_bottom = 5;
+ bool is_real_snapshot = 6;
+ int32 windowing_mode = 7;
+ int32 system_ui_visibility = 8 [deprecated=true];
+ bool is_translucent = 9;
+ string top_activity_component = 10;
+ // deprecated because original width and height are stored now instead of the scale.
+ float legacy_scale = 11 [deprecated=true];
+ int64 id = 12;
+ int32 rotation = 13;
+ // The task width when the snapshot was taken
+ int32 task_width = 14;
+ // The task height when the snapshot was taken
+ int32 task_height = 15;
+ int32 appearance = 16;
+ int32 letterbox_inset_left = 17;
+ int32 letterbox_inset_top = 18;
+ int32 letterbox_inset_right = 19;
+ int32 letterbox_inset_bottom = 20;
+}
+
+// Persistent letterboxing configurations
+message LetterboxProto {
+
+ // Possible values for the letterbox horizontal reachability
+ enum LetterboxHorizontalReachability {
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT = 0;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT = 2;
+ }
+
+ // Possible values for the letterbox vertical reachability
+ enum LetterboxVerticalReachability {
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP = 0;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;
+ }
+
+ // Represents the current horizontal position for the letterboxed activity
+ LetterboxHorizontalReachability letterbox_position_for_horizontal_reachability = 1;
+ // Represents the current vertical position for the letterboxed activity
+ LetterboxVerticalReachability letterbox_position_for_vertical_reachability = 2;
+} \ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 1df382fbd76f..f35de17088d1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -176,6 +176,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
private boolean mSendMotionEvents;
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>(0);
boolean mRequestFilterKeyEvents;
boolean mRetrieveInteractiveWindows;
@@ -2369,9 +2370,17 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
+ mServiceDetectsGestures.put(displayId, mode);
mSystemSupport.setServiceDetectsGesturesEnabled(displayId, mode);
}
+ public boolean isServiceDetectsGesturesEnabled(int displayId) {
+ if (mServiceDetectsGestures.contains(displayId)) {
+ return mServiceDetectsGestures.get(displayId);
+ }
+ return false;
+ }
+
public void requestTouchExploration(int displayId) {
mSystemSupport.requestTouchExploration(displayId);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 75724bffabf8..d80117d8d8ba 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -176,6 +176,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
private int mEnabledFeatures;
+ // Display-specific features
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>();
private final SparseArray<EventStreamState> mMouseStreamStates = new SparseArray<>(0);
private final SparseArray<EventStreamState> mTouchScreenStreamStates = new SparseArray<>(0);
@@ -458,7 +460,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
final Context displayContext = mContext.createDisplayContext(display);
final int displayId = display.getDisplayId();
-
+ if (!mServiceDetectsGestures.contains(displayId)) {
+ mServiceDetectsGestures.put(displayId, false);
+ }
if ((mEnabledFeatures & FLAG_FEATURE_AUTOCLICK) != 0) {
if (mAutoclickController == null) {
mAutoclickController = new AutoclickController(
@@ -481,6 +485,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
if ((mEnabledFeatures & FLAG_SEND_MOTION_EVENTS) != 0) {
explorer.setSendMotionEventsEnabled(true);
}
+ explorer.setServiceDetectsGestures(mServiceDetectsGestures.get(displayId));
addFirstEventHandler(displayId, explorer);
mTouchExplorer.put(displayId, explorer);
}
@@ -897,6 +902,11 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
if (mTouchExplorer.contains(displayId)) {
mTouchExplorer.get(displayId).setServiceDetectsGestures(mode);
}
+ mServiceDetectsGestures.put(displayId, mode);
+ }
+
+ public void resetServiceDetectsGestures() {
+ mServiceDetectsGestures.clear();
}
public void requestTouchExploration(int displayId) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 085a58909b6d..47b415630de8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1695,31 +1695,34 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private boolean scheduleNotifyMotionEvent(MotionEvent event) {
+ boolean result = false;
+ int displayId = event.getDisplayId();
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestTouchExplorationMode) {
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
service.notifyMotionEvent(event);
- return true;
+ result = true;
}
}
}
- return false;
+ return result;
}
private boolean scheduleNotifyTouchState(int displayId, int touchState) {
+ boolean result = false;
synchronized (mLock) {
AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestTouchExplorationMode) {
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
service.notifyTouchState(displayId, touchState);
- return true;
+ result = true;
}
}
}
- return false;
+ return result;
}
private void notifyClearAccessibilityCacheLocked() {
@@ -2292,8 +2295,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (!mHasInputFilter) {
mHasInputFilter = true;
if (mInputFilter == null) {
- mInputFilter = new AccessibilityInputFilter(mContext,
- AccessibilityManagerService.this);
+ mInputFilter =
+ new AccessibilityInputFilter(
+ mContext, AccessibilityManagerService.this);
}
inputFilter = mInputFilter;
setInputFilter = true;
@@ -2303,6 +2307,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (mHasInputFilter) {
mHasInputFilter = false;
mInputFilter.setUserAndEnabledFeatures(userState.mUserId, 0);
+ mInputFilter.resetServiceDetectsGestures();
+ if (userState.isTouchExplorationEnabledLocked()) {
+ // Service gesture detection is turned on and off on a per-display
+ // basis.
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (Display display : displays) {
+ int displayId = display.getDisplayId();
+ boolean mode = userState.isServiceDetectsGesturesEnabled(displayId);
+ mInputFilter.setServiceDetectsGesturesEnabled(displayId, mode);
+ }
+ }
inputFilter = null;
setInputFilter = true;
}
@@ -2618,6 +2633,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Binder.restoreCallingIdentity(identity);
}
}
+ // Service gesture detection is turned on and off on a per-display
+ // basis.
+ userState.resetServiceDetectsGestures();
+ final ArrayList<Display> displays = getValidDisplayList();
+ for (AccessibilityServiceConnection service: userState.mBoundServices) {
+ for (Display display : displays) {
+ int displayId = display.getDisplayId();
+ if (service.isServiceDetectsGesturesEnabled(displayId)) {
+ userState.setServiceDetectsGesturesEnabled(displayId, true);
+ }
+ }
+ }
userState.setServiceHandlesDoubleTapLocked(serviceHandlesDoubleTapEnabled);
userState.setMultiFingerGesturesLocked(requestMultiFingerGestures);
userState.setTwoFingerPassthroughLocked(requestTwoFingerPassthrough);
@@ -4342,6 +4369,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void setServiceDetectsGesturesInternal(int displayId, boolean mode) {
synchronized (mLock) {
+ getCurrentUserStateLocked().setServiceDetectsGesturesEnabled(displayId, mode);
if (mHasInputFilter && mInputFilter != null) {
mInputFilter.setServiceDetectsGesturesEnabled(displayId, mode);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 0cb7209c187a..0db169fd76c3 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -44,6 +44,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
@@ -118,6 +119,7 @@ class AccessibilityUserState {
private boolean mRequestMultiFingerGestures;
private boolean mRequestTwoFingerPassthrough;
private boolean mSendMotionEventsEnabled;
+ private SparseArray<Boolean> mServiceDetectsGestures = new SparseArray<>(0);
private int mUserInteractiveUiTimeout;
private int mUserNonInteractiveUiTimeout;
private int mNonInteractiveUiTimeout = 0;
@@ -991,4 +993,19 @@ class AccessibilityUserState {
mFocusStrokeWidth = strokeWidth;
mFocusColor = color;
}
+
+ public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) {
+ mServiceDetectsGestures.put(displayId, mode);
+ }
+
+ public void resetServiceDetectsGestures() {
+ mServiceDetectsGestures.clear();
+ }
+
+ public boolean isServiceDetectsGesturesEnabled(int displayId) {
+ if (mServiceDetectsGestures.contains(displayId)) {
+ return mServiceDetectsGestures.get(displayId);
+ }
+ return false;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index eac98f2b2337..f0492a8b58b4 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -376,6 +376,16 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
return;
}
+ // In some cases there may not be a monitor passed in when creating this task. So, if we
+ // don't have one already we ask the transport for a monitor.
+ if (mMonitor == null) {
+ try {
+ mMonitor = transport.getBackupManagerMonitor();
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve monitor from transport");
+ }
+ }
+
// Set up to send data to the transport
final int N = mPackages.size();
final byte[] buffer = new byte[8192];
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 03796eae3b8e..95cc289d58b7 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -21,6 +21,7 @@ import static com.android.server.backup.BackupManagerService.MORE_DEBUG;
import static com.android.server.backup.BackupManagerService.TAG;
import android.app.backup.BackupManager.OperationType;
+import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreSet;
import android.os.Handler;
import android.os.HandlerThread;
@@ -203,6 +204,14 @@ public class BackupHandler extends Handler {
}
}
+ // Ask the transport for a monitor that will be used to relay log events back to it.
+ IBackupManagerMonitor monitor = null;
+ try {
+ monitor = transport.getBackupManagerMonitor();
+ } catch (RemoteException e) {
+ Slog.i(TAG, "Failed to retrieve monitor from transport");
+ }
+
// At this point, we have started a new journal file, and the old
// file identity is being passed to the backup processing task.
// When it completes successfully, that old journal file will be
@@ -225,7 +234,7 @@ public class BackupHandler extends Handler {
queue,
oldJournal,
/* observer */ null,
- /* monitor */ null,
+ monitor,
listener,
Collections.emptyList(),
/* userInitiated */ false,
diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
index 237a3faf220e..40d7cad9405a 100644
--- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -18,10 +18,12 @@ package com.android.server.backup.transport;
import android.annotation.Nullable;
import android.app.backup.BackupTransport;
+import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.Intent;
import android.content.pm.PackageInfo;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
@@ -363,6 +365,15 @@ public class BackupTransportClient {
}
/**
+ * See {@link IBackupTransport#getBackupManagerMonitor()}
+ */
+ public IBackupManagerMonitor getBackupManagerMonitor() throws RemoteException {
+ AndroidFuture<IBackupManagerMonitor> resultFuture = mTransportFutures.newFuture();
+ mTransportBinder.getBackupManagerMonitor(resultFuture);
+ return IBackupManagerMonitor.Stub.asInterface((IBinder) getFutureResult(resultFuture));
+ }
+
+ /**
* Allows the {@link TransportConnection} to notify this client
* if the underlying transport has become unusable. If that happens
* we want to cancel all active futures or callbacks.
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 fc628cfdced2..000bafe1d650 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -44,6 +44,7 @@ import android.view.Display;
import android.window.DisplayWindowPolicyController;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.BlockedAppStreamingActivity;
import java.util.List;
@@ -112,7 +113,9 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
final ArraySet<Integer> mRunningUids = new ArraySet<>();
@Nullable private final ActivityListener mActivityListener;
private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListener =
+ @NonNull
+ @GuardedBy("mGenericWindowPolicyControllerLock")
+ private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
new ArraySet<>();
@Nullable
private final @AssociationRequest.DeviceProfile String mDeviceProfile;
@@ -178,12 +181,16 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
/** Register a listener for running applications changes. */
public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
- mRunningAppsChangedListener.add(listener);
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mRunningAppsChangedListeners.add(listener);
+ }
}
/** Unregister a listener for running applications changes. */
public void unregisterRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
- mRunningAppsChangedListener.remove(listener);
+ synchronized (mGenericWindowPolicyControllerLock) {
+ mRunningAppsChangedListeners.remove(listener);
+ }
}
@Override
@@ -283,12 +290,16 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
// Post callback on the main thread so it doesn't block activity launching
mHandler.post(() -> mActivityListener.onDisplayEmpty(mDisplayId));
}
- }
- mHandler.post(() -> {
- for (RunningAppsChangedListener listener : mRunningAppsChangedListener) {
- listener.onRunningAppsChanged(runningUids);
+ if (!mRunningAppsChangedListeners.isEmpty()) {
+ final ArraySet<RunningAppsChangedListener> listeners =
+ new ArraySet<>(mRunningAppsChangedListeners);
+ mHandler.post(() -> {
+ for (RunningAppsChangedListener listener : listeners) {
+ listener.onRunningAppsChanged(runningUids);
+ }
+ });
}
- });
+ }
}
@Override
@@ -354,4 +365,11 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
}
return true;
}
+
+ @VisibleForTesting
+ int getRunningAppsChangedListenersSizeForTesting() {
+ synchronized (mGenericWindowPolicyControllerLock) {
+ return mRunningAppsChangedListeners.size();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index 933d2596aed8..e40f001f27d5 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -473,6 +473,18 @@ public abstract class SystemService {
}
/**
+ * The {@link UserManager#isUserVisible() user visibility} changed.
+ *
+ * <p>This callback is called before the user starts or is switched to (or after it stops), when
+ * its visibility changed because of that action.
+ *
+ * @hide
+ */
+ // NOTE: change visible to int if this method becomes a @SystemApi
+ public void onUserVisibilityChanged(@NonNull TargetUser user, boolean visible) {
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called prior to sending the SHUTDOWN
* broadcast to the user; it is a good place to stop making use of any resources of that
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 1a8cf0b07cb6..83d86cdc05c6 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -75,13 +75,17 @@ public final class SystemServiceManager implements Dumpable {
// Constants used on onUser(...)
// NOTE: do not change their values, as they're used on Trace calls and changes might break
// performance tests that rely on them.
- private static final String USER_STARTING = "Start"; // Logged as onStartUser
- private static final String USER_UNLOCKING = "Unlocking"; // Logged as onUnlockingUser
- private static final String USER_UNLOCKED = "Unlocked"; // Logged as onUnlockedUser
- private static final String USER_SWITCHING = "Switch"; // Logged as onSwitchUser
- private static final String USER_STOPPING = "Stop"; // Logged as onStopUser
- private static final String USER_STOPPED = "Cleanup"; // Logged as onCleanupUser
- private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onCompletedEventUser
+ private static final String USER_STARTING = "Start"; // Logged as onUserStarting()
+ private static final String USER_UNLOCKING = "Unlocking"; // Logged as onUserUnlocking()
+ private static final String USER_UNLOCKED = "Unlocked"; // Logged as onUserUnlocked()
+ private static final String USER_SWITCHING = "Switch"; // Logged as onUserSwitching()
+ private static final String USER_STOPPING = "Stop"; // Logged as onUserStopping()
+ private static final String USER_STOPPED = "Cleanup"; // Logged as onUserStopped()
+ private static final String USER_COMPLETED_EVENT = "CompletedEvent"; // onUserCompletedEvent()
+ private static final String USER_VISIBLE = "Visible"; // Logged on onUserVisible() and
+ // onUserStarting() (when visible is true)
+ private static final String USER_INVISIBLE = "Invisible"; // Logged on onUserStopping()
+ // (when visibilityChanged is true)
// The default number of threads to use if lifecycle thread pool is enabled.
private static final int DEFAULT_MAX_USER_POOL_THREADS = 3;
@@ -350,18 +354,41 @@ public final class SystemServiceManager implements Dumpable {
/**
* Starts the given user.
*/
- public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
+ public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId,
+ boolean visible) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId, visible ? 1 : 0);
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
mTargetUsers.put(userId, targetUser);
}
+ if (visible) {
+ // Must send the user visiiblity change first, for 2 reasons:
+ // 1. Automotive need to update the user-zone mapping ASAP and it's one of the few
+ // services listening to this event (OTOH, there are manyy listeners to USER_STARTING
+ // and some can take a while to process it)
+ // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
+ // called onUserSwitching(), so calling it before onUserStarting() make it more
+ // consistent with that
+ onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
+ }
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
/**
+ * Updates the user visibility.
+ *
+ * <p><b>NOTE: </b>this method should only be called when a user that is already running become
+ * visible; if the user is starting visible, callers should call
+ * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead
+ */
+ public void onUserVisible(@UserIdInt int userId) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
+ onUser(USER_VISIBLE, userId);
+ }
+
+ /**
* Unlocks the given user.
*/
public void onUserUnlocking(@UserIdInt int userId) {
@@ -408,9 +435,12 @@ public final class SystemServiceManager implements Dumpable {
/**
* Stops the given user.
*/
- public void onUserStopping(@UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId);
+ public void onUserStopping(@UserIdInt int userId, boolean visibilityChanged) {
+ EventLog.writeEvent(EventLogTags.SSM_USER_STOPPING, userId, visibilityChanged ? 1 : 0);
onUser(USER_STOPPING, userId);
+ if (visibilityChanged) {
+ onUser(USER_INVISIBLE, userId);
+ }
}
/**
@@ -456,13 +486,12 @@ public final class SystemServiceManager implements Dumpable {
TargetUser targetUser = getTargetUser(userId);
Preconditions.checkState(targetUser != null, "No TargetUser for " + userId);
- onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null,
- targetUser);
+ onUser(TimingsTraceAndSlog.newAsyncLog(), onWhat, /* prevUser= */ null, targetUser);
}
private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
@Nullable TargetUser prevUser, @NonNull TargetUser curUser) {
- onUser(t, onWhat, prevUser, curUser, /* completedEventType=*/ null);
+ onUser(t, onWhat, prevUser, curUser, /* completedEventType= */ null);
}
private void onUser(@NonNull TimingsTraceAndSlog t, @NonNull String onWhat,
@@ -534,6 +563,12 @@ public final class SystemServiceManager implements Dumpable {
threadPool.submit(getOnUserCompletedEventRunnable(
t, service, serviceName, curUser, completedEventType));
break;
+ case USER_VISIBLE:
+ service.onUserVisibilityChanged(curUser, /* visible= */ true);
+ break;
+ case USER_INVISIBLE:
+ service.onUserVisibilityChanged(curUser, /* visible= */ false);
+ break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b166adc9a828..3032f17c2597 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3891,24 +3891,29 @@ public class ActivityManagerService extends IActivityManager.Stub
finishForceStopPackageLocked(packageName, appInfo.uid);
}
}
- final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
- Uri.fromParts("package", packageName, null));
- intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- intent.putExtra(Intent.EXTRA_UID, (appInfo != null) ? appInfo.uid : -1);
- intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
- final int[] visibilityAllowList =
- mPackageManagerInt.getVisibilityAllowList(packageName, resolvedUserId);
- if (isInstantApp) {
- intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
- broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent,
- null, null, null, 0, null, null, permission.ACCESS_INSTANT_APPS,
- null, false, false, resolvedUserId, false, null,
- visibilityAllowList);
- } else {
- broadcastIntentInPackage("android", null, SYSTEM_UID, uid, pid, intent,
- null, null, null, 0, null, null, null, null, false, false,
- resolvedUserId, false, null, visibilityAllowList);
+
+ if (succeeded) {
+ final Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
+ Uri.fromParts("package", packageName, null /* fragment */));
+ intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ intent.putExtra(Intent.EXTRA_UID,
+ (appInfo != null) ? appInfo.uid : INVALID_UID);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, resolvedUserId);
+ if (isInstantApp) {
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ }
+ final int[] visibilityAllowList = mPackageManagerInt.getVisibilityAllowList(
+ packageName, resolvedUserId);
+
+ broadcastIntentInPackage("android", null /* featureId */,
+ SYSTEM_UID, uid, pid, intent, null /* resolvedType */,
+ null /* resultToApp */, null /* resultTo */, 0 /* resultCode */,
+ null /* resultData */, null /* resultExtras */,
+ isInstantApp ? permission.ACCESS_INSTANT_APPS : null,
+ null /* bOptions */, false /* serialized */, false /* sticky */,
+ resolvedUserId, false /* allowBackgroundActivityStarts */,
+ null /* backgroundActivityStartsToken */, visibilityAllowList);
}
if (observer != null) {
@@ -8346,14 +8351,14 @@ public class ActivityManagerService extends IActivityManager.Stub
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
Integer.toString(currentUserId), currentUserId);
- // On Automotive, at this point the system user has already been started and unlocked,
- // and some of the tasks we do here have already been done. So skip those in that case.
- // TODO(b/132262830, b/203885241): this workdound shouldn't be necessary once we move the
- // headless-user start logic to UserManager-land
+ // On Automotive / Headless System User Mode, at this point the system user has already been
+ // started and unlocked, and some of the tasks we do here have already been done. So skip
+ // those in that case.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
+ // start logic to UserManager-land
final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
-
if (bootingSystemUser) {
- mSystemServiceManager.onUserStarting(t, currentUserId);
+ mUserController.onSystemUserStarting();
}
synchronized (this) {
@@ -13860,6 +13865,9 @@ public class ActivityManagerService extends IActivityManager.Stub
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList,
@Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+ // Ensure all internal loopers are registered for idle checks
+ BroadcastLoopers.addMyLooper();
+
if ((resultTo != null) && (resultToApp == null)) {
if (resultTo.asBinder() instanceof BinderProxy) {
// Warn when requesting results without a way to deliver them
@@ -18110,6 +18118,7 @@ public class ActivityManagerService extends IActivityManager.Stub
public void waitForBroadcastIdle(@Nullable PrintWriter pw) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
+ BroadcastLoopers.waitForIdle(pw);
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForIdle(pw);
}
@@ -18121,6 +18130,7 @@ public class ActivityManagerService extends IActivityManager.Stub
public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
+ BroadcastLoopers.waitForIdle(pw);
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForBarrier(pw);
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 4590c859a909..417a0e5ede83 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -133,7 +133,7 @@ public class BroadcastConstants {
*/
public boolean MODERN_QUEUE_ENABLED = DEFAULT_MODERN_QUEUE_ENABLED;
private static final String KEY_MODERN_QUEUE_ENABLED = "modern_queue_enabled";
- private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = false;
+ private static final boolean DEFAULT_MODERN_QUEUE_ENABLED = true;
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of process queues to
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
new file mode 100644
index 000000000000..bebb48473fc3
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -0,0 +1,109 @@
+/*
+ * 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.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Collection of {@link Looper} that are known to be used for broadcast dispatch
+ * within the system. This collection can be useful for callers interested in
+ * confirming that all pending broadcasts have been successfully enqueued.
+ */
+public class BroadcastLoopers {
+ private static final String TAG = "BroadcastLoopers";
+
+ private static final ArraySet<Looper> sLoopers = new ArraySet<>();
+
+ /**
+ * Register the given {@link Looper} as possibly having messages that will
+ * dispatch broadcasts.
+ */
+ public static void addLooper(@NonNull Looper looper) {
+ synchronized (sLoopers) {
+ sLoopers.add(Objects.requireNonNull(looper));
+ }
+ }
+
+ /**
+ * If the current thread is hosting a {@link Looper}, then register it as
+ * possibly having messages that will dispatch broadcasts.
+ */
+ public static void addMyLooper() {
+ final Looper looper = Looper.myLooper();
+ if (looper != null) {
+ synchronized (sLoopers) {
+ if (sLoopers.add(looper)) {
+ Slog.w(TAG, "Found previously unknown looper " + looper.getThread());
+ }
+ }
+ }
+ }
+
+ /**
+ * Wait for all registered {@link Looper} instances to become idle, as
+ * defined by {@link MessageQueue#isIdle()}. Note that {@link Message#when}
+ * still in the future are ignored for the purposes of the idle test.
+ */
+ public static void waitForIdle(@Nullable PrintWriter pw) {
+ 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();
+ if (queue.isIdle()) {
+ latch.countDown();
+ } else {
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ return false;
+ });
+ }
+ }
+ }
+
+ long lastPrint = 0;
+ while (latch.getCount() > 0) {
+ final long now = SystemClock.uptimeMillis();
+ if (now >= lastPrint + 1000) {
+ lastPrint = now;
+ logv("Waiting for " + latch.getCount() + " loopers to drain...", pw);
+ }
+ SystemClock.sleep(100);
+ }
+ logv("Loopers drained!", pw);
+ }
+
+ private static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
+ Slog.v(TAG, msg);
+ if (pw != null) {
+ pw.println(msg);
+ pw.flush();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 5123517e272d..f7d24e9b8b4e 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -85,9 +85,16 @@ class BroadcastProcessQueue {
@Nullable ProcessRecord app;
/**
- * Track name to use for {@link Trace} events.
+ * Track name to use for {@link Trace} events, defined as part of upgrading
+ * into a running slot.
*/
- @Nullable String traceTrackName;
+ @Nullable String runningTraceTrackName;
+
+ /**
+ * Flag indicating if this process should be OOM adjusted, defined as part
+ * of upgrading into a running slot.
+ */
+ boolean runningOomAdjusted;
/**
* Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
@@ -141,7 +148,8 @@ class BroadcastProcessQueue {
private boolean mActiveViaColdStart;
/**
- * Count of {@link #mPending} broadcasts of these various flavors.
+ * Count of {@link #mPending} and {@link #mPendingUrgent} broadcasts of
+ * these various flavors.
*/
private int mCountForeground;
private int mCountOrdered;
@@ -150,6 +158,7 @@ class BroadcastProcessQueue {
private int mCountInteractive;
private int mCountResultTo;
private int mCountInstrumented;
+ private int mCountManifest;
private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
private @Reason int mRunnableAtReason = REASON_EMPTY;
@@ -206,7 +215,7 @@ class BroadcastProcessQueue {
// with implicit responsiveness expectations.
final ArrayDeque<SomeArgs> queue = record.isUrgent() ? mPendingUrgent : mPending;
queue.addLast(newBroadcastArgs);
- onBroadcastEnqueued(record);
+ onBroadcastEnqueued(record, recordIndex);
}
/**
@@ -224,7 +233,8 @@ class BroadcastProcessQueue {
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord testRecord = (BroadcastRecord) args.arg1;
- final Object testReceiver = testRecord.receivers.get(args.argi1);
+ final int testRecordIndex = args.argi1;
+ final Object testReceiver = testRecord.receivers.get(testRecordIndex);
if ((record.callingUid == testRecord.callingUid)
&& (record.userId == testRecord.userId)
&& record.intent.filterEquals(testRecord.intent)
@@ -233,8 +243,8 @@ class BroadcastProcessQueue {
args.arg1 = record;
args.argi1 = recordIndex;
args.argi2 = blockedUntilTerminalCount;
- onBroadcastDequeued(testRecord);
- onBroadcastEnqueued(record);
+ onBroadcastDequeued(testRecord, testRecordIndex);
+ onBroadcastEnqueued(record, recordIndex);
return true;
}
}
@@ -284,13 +294,13 @@ class BroadcastProcessQueue {
while (it.hasNext()) {
final SomeArgs args = it.next();
final BroadcastRecord record = (BroadcastRecord) args.arg1;
- final int index = args.argi1;
- if (predicate.test(record, index)) {
- consumer.accept(record, index);
+ final int recordIndex = args.argi1;
+ if (predicate.test(record, recordIndex)) {
+ consumer.accept(record, recordIndex);
if (andRemove) {
args.recycle();
it.remove();
- onBroadcastDequeued(record);
+ onBroadcastDequeued(record, recordIndex);
}
didSomething = true;
}
@@ -339,7 +349,7 @@ class BroadcastProcessQueue {
* Return if we know of an actively running "warm" process for this queue.
*/
public boolean isProcessWarm() {
- return (app != null) && (app.getThread() != null) && !app.isKilled();
+ return (app != null) && (app.getOnewayThread() != null) && !app.isKilled();
}
public int getPreferredSchedulingGroupLocked() {
@@ -385,7 +395,7 @@ class BroadcastProcessQueue {
mActiveCountSinceIdle++;
mActiveViaColdStart = false;
next.recycle();
- onBroadcastDequeued(mActive);
+ onBroadcastDequeued(mActive, mActiveIndex);
}
/**
@@ -403,7 +413,7 @@ class BroadcastProcessQueue {
/**
* Update summary statistics when the given record has been enqueued.
*/
- private void onBroadcastEnqueued(@NonNull BroadcastRecord record) {
+ private void onBroadcastEnqueued(@NonNull BroadcastRecord record, int recordIndex) {
if (record.isForeground()) {
mCountForeground++;
}
@@ -425,13 +435,16 @@ class BroadcastProcessQueue {
if (record.callerInstrumented) {
mCountInstrumented++;
}
+ if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
+ mCountManifest++;
+ }
invalidateRunnableAt();
}
/**
* Update summary statistics when the given record has been dequeued.
*/
- private void onBroadcastDequeued(@NonNull BroadcastRecord record) {
+ private void onBroadcastDequeued(@NonNull BroadcastRecord record, int recordIndex) {
if (record.isForeground()) {
mCountForeground--;
}
@@ -453,34 +466,37 @@ class BroadcastProcessQueue {
if (record.callerInstrumented) {
mCountInstrumented--;
}
+ if (record.receivers.get(recordIndex) instanceof ResolveInfo) {
+ mCountManifest--;
+ }
invalidateRunnableAt();
}
public void traceProcessStartingBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, toShortString() + " starting", hashCode());
+ runningTraceTrackName, toShortString() + " starting", hashCode());
}
public void traceProcessRunningBegin() {
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, toShortString() + " running", hashCode());
+ runningTraceTrackName, toShortString() + " running", hashCode());
}
public void traceProcessEnd() {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, hashCode());
+ runningTraceTrackName, hashCode());
}
public void traceActiveBegin() {
final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, mActive.toShortString() + " scheduled", cookie);
+ runningTraceTrackName, mActive.toShortString() + " scheduled", cookie);
}
public void traceActiveEnd() {
final int cookie = mActive.receivers.get(mActiveIndex).hashCode();
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- traceTrackName, cookie);
+ runningTraceTrackName, cookie);
}
/**
@@ -540,6 +556,14 @@ class BroadcastProcessQueue {
}
/**
+ * Quickly determine if this queue has broadcasts waiting to be delivered to
+ * manifest receivers, which indicates we should request an OOM adjust.
+ */
+ public boolean isPendingManifest() {
+ return mCountManifest > 0;
+ }
+
+ /**
* Quickly determine if this queue has broadcasts that are still waiting to
* be delivered at some point in the future.
*/
@@ -807,7 +831,7 @@ class BroadcastProcessQueue {
@NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
- if ((mActive == null) && mPending.isEmpty()) return;
+ if ((mActive == null) && isEmpty()) return;
pw.print(toShortString());
if (isRunnable()) {
@@ -823,6 +847,10 @@ class BroadcastProcessQueue {
if (mActive != null) {
dumpRecord(now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
}
+ for (SomeArgs args : mPendingUrgent) {
+ final BroadcastRecord r = (BroadcastRecord) args.arg1;
+ dumpRecord(now, pw, r, args.argi1, args.argi2);
+ }
for (SomeArgs args : mPending) {
final BroadcastRecord r = (BroadcastRecord) args.arg1;
dumpRecord(now, pw, r, args.argi1, args.argi2);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.md b/services/core/java/com/android/server/am/BroadcastQueue.md
new file mode 100644
index 000000000000..81317932ef9b
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastQueue.md
@@ -0,0 +1,98 @@
+# Broadcast Queue Design
+
+Broadcast intents are one of the major building blocks of the Android platform,
+generally intended for asynchronous notification of events. There are three
+flavors of intents that can be broadcast:
+
+* **Normal** broadcast intents are dispatched to relevant receivers.
+* **Ordered** broadcast intents are dispatched in a specific order to
+receivers, where each receiver has the opportunity to influence the final
+"result" of a broadcast, including aborting delivery to any remaining receivers.
+* **Sticky** broadcast intents are dispatched to relevant receivers, and are
+then retained internally for immediate dispatch to any future receivers. (This
+capability has been deprecated and its use is discouraged due to its system
+health impact.)
+
+And there are there two ways to receive these intents:
+
+* Registered receivers (via `Context.registerReceiver()` methods) are
+dynamically requested by a running app to receive intents. These requests are
+only maintained while the process is running, and are discarded at process
+death.
+* Manifest receivers (via the `<receiver>` tag in `AndroidManifest.xml`) are
+statically requested by an app to receive intents. These requests are delivered
+regardless of process running state, and have the ability to cold-start a
+process that isn't currently running.
+
+## Per-process queues
+
+The design of `BroadcastQueueModernImpl` is centered around maintaining a
+separate `BroadcastProcessQueue` instance for each potential process on the
+device. At this level, a process refers to the `android:process` attributes
+defined in `AndroidManifest.xml` files, which means it can be defined and
+populated regardless of the process state. (For example, a given
+`android:process` can have multiple `ProcessRecord`/PIDs defined as it's
+launched, killed, and relaunched over long periods of time.)
+
+Each per-process queue has the concept of a _runnable at_ timestamp when it's
+next eligible for execution, and that value can be influenced by a wide range
+of policies, such as:
+
+* Which broadcasts are pending dispatch to a given process. For example, an
+"urgent" broadcast typically results in an earlier _runnable at_ time, or a
+"delayed" broadcast typically results in a later _runnable at_ time.
+* Current state of the process or UID. For example, a "cached" process
+typically results in a later _runnable at_ time, or an "instrumented" process
+typically results in an earlier _runnable at_ time.
+* Blocked waiting for an earlier receiver to complete. For example, an
+"ordered" or "prioritized" broadcast typically results in a _not currently
+runnable_ value.
+
+Each per-process queue represents a single remote `ApplicationThread`, and we
+only dispatch a single broadcast at a time to each process to ensure developers
+see consistent ordering of broadcast events. The flexible _runnable at_
+policies above mean that no inter-process ordering guarantees are provided,
+except for those explicitly provided by "ordered" or "prioritized" broadcasts.
+
+## Parallel dispatch
+
+Given a collection of per-process queues with valid _runnable at_ timestamps,
+BroadcastQueueModernImpl is then willing to promote those _runnable_ queues
+into a _running_ state. We choose the next per-process queue to promote based
+on the sorted ordering of the _runnable at_ timestamps, selecting the
+longest-waiting process first, which aims to reduce overall broadcast dispatch
+latency.
+
+To preserve system health, at most
+`BroadcastConstants.MAX_RUNNING_PROCESS_QUEUES` processes are allowed to be in
+the _running_ state at any given time, and at most one process is allowed to be
+_cold started_ at any given time. (For background, _cold starting_ a process
+by forking and specializing the zygote is a relatively heavy operation, so
+limiting ourselves to a single pending _cold start_ reduces system-wide
+resource contention.)
+
+After each broadcast is dispatched to a given process, we consider dispatching
+any additional pending broadcasts to that process, aimed at batching dispatch
+to better amortize the cost of OOM adjustments.
+
+## Starvation considerations
+
+Careful attention is given to several types of potential resource starvation,
+along with the mechanisms of mitigation:
+
+* A per-process queue that has a delayed _runnable at_ policy applied can risk
+growing very large. This is mitigated by
+`BroadcastConstants.MAX_PENDING_BROADCASTS` bypassing any delays when the queue
+grows too large.
+* A per-process queue that has a large number of pending broadcasts can risk
+monopolizing one of the limited _runnable_ slots. This is mitigated by
+`BroadcastConstants.MAX_RUNNING_ACTIVE_BROADCASTS` being used to temporarily
+"retire" a running process to give other processes a chance to run.
+* An "urgent" broadcast dispatched to a process with a large backlog of
+"non-urgent" broadcasts can risk large dispatch latencies. This is mitigated
+by maintaining a separate `mPendingUrgent` queue of urgent events, which we
+prefer to dispatch before the normal `mPending` queue.
+* A process with a scheduled broadcast desires to execute, but heavy CPU
+contention can risk the process not receiving enough resources before an ANR
+timeout is triggered. This is mitigated by extending the "soft" ANR timeout by
+up to double the original timeout length.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 4c831bd47ee4..9e9eb71db4e5 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -58,6 +58,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
@@ -364,6 +365,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
BroadcastProcessQueue nextQueue = queue.runnableAtNext;
final long runnableAt = queue.getRunnableAt();
+ // When broadcasts are skipped or failed during list traversal, we
+ // might encounter a queue that is no longer runnable; skip it
+ if (!queue.isRunnable()) {
+ queue = nextQueue;
+ continue;
+ }
+
// If queues beyond this point aren't ready to run yet, schedule
// another pass when they'll be runnable
if (runnableAt > now && !waitingFor) {
@@ -401,7 +409,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mRunnableHead = removeFromRunnableList(mRunnableHead, queue);
// Emit all trace events for this process into a consistent track
- queue.traceTrackName = TAG + ".mRunning[" + queueIndex + "]";
+ queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]";
+ queue.runningOomAdjusted = queue.isPendingManifest();
// If we're already warm, schedule next pending broadcast now;
// otherwise we'll wait for the cold start to circle back around
@@ -415,9 +424,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
scheduleReceiverColdLocked(queue);
}
- // We've moved at least one process into running state above, so we
- // need to kick off an OOM adjustment pass
- updateOomAdj = true;
+ // Only kick off an OOM adjustment pass if needed
+ updateOomAdj |= queue.runningOomAdjusted;
// Move to considering next runnable queue
queue = nextQueue;
@@ -543,16 +551,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}, mBroadcastConsumerSkipAndCanceled, true);
}
- final int policy = (r.options != null)
- ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
- if (policy == BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) {
- forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
- // We only allow caller to remove broadcasts they enqueued
- return (r.callingUid == testRecord.callingUid)
- && (r.userId == testRecord.userId)
- && r.matchesDeliveryGroup(testRecord);
- }, mBroadcastConsumerSkipAndCanceled, true);
- }
+ applyDeliveryGroupPolicy(r);
if (r.isReplacePending()) {
// Leave the skipped broadcasts intact in queue, so that we can
@@ -609,6 +608,41 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
+ private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
+ final int policy = (r.options != null)
+ ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
+ final BroadcastConsumer broadcastConsumer;
+ switch (policy) {
+ case BroadcastOptions.DELIVERY_GROUP_POLICY_ALL:
+ // Older broadcasts need to be left as is in this case, so nothing more to do.
+ return;
+ case BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT:
+ broadcastConsumer = mBroadcastConsumerSkipAndCanceled;
+ break;
+ case BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED:
+ final BundleMerger extrasMerger = r.options.getDeliveryGroupExtrasMerger();
+ if (extrasMerger == null) {
+ // Extras merger is required to be able to merge the extras. So, if it's not
+ // supplied, then ignore the delivery group policy.
+ return;
+ }
+ broadcastConsumer = (record, recordIndex) -> {
+ r.intent.mergeExtras(record.intent, extrasMerger);
+ mBroadcastConsumerSkipAndCanceled.accept(record, recordIndex);
+ };
+ break;
+ default:
+ logw("Unknown delivery group policy: " + policy);
+ return;
+ }
+ forEachMatchingBroadcast(QUEUE_PREDICATE_ANY, (testRecord, testIndex) -> {
+ // We only allow caller to remove broadcasts they enqueued
+ return (r.callingUid == testRecord.callingUid)
+ && (r.userId == testRecord.userId)
+ && r.matchesDeliveryGroup(testRecord);
+ }, broadcastConsumer, true);
+ }
+
/**
* Schedule the currently active broadcast on the given queue when we know
* the process is cold. This kicks off a cold start and will eventually call
@@ -736,7 +770,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
- final IApplicationThread thread = app.getThread();
+ final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
try {
if (receiver instanceof BroadcastFilter) {
@@ -777,7 +811,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void scheduleResultTo(@NonNull BroadcastRecord r) {
if ((r.resultToApp == null) || (r.resultTo == null)) return;
final ProcessRecord app = r.resultToApp;
- final IApplicationThread thread = app.getThread();
+ final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
app, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -1245,8 +1279,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (queue.app != null) {
queue.app.mReceivers.incrementCurReceivers();
- queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
-
// Don't bump its LRU position if it's in the background restricted.
if (mService.mInternal.getRestrictionLevel(
queue.uid) < ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
@@ -1256,7 +1288,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(queue.app,
OOM_ADJ_REASON_START_RECEIVER);
- mService.enqueueOomAdjTargetLocked(queue.app);
+ if (queue.runningOomAdjusted) {
+ queue.app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+ mService.enqueueOomAdjTargetLocked(queue.app);
+ }
}
}
@@ -1266,10 +1301,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
*/
private void notifyStoppedRunning(@NonNull BroadcastProcessQueue queue) {
if (queue.app != null) {
- // Update during our next pass; no need for an immediate update
- mService.enqueueOomAdjTargetLocked(queue.app);
-
queue.app.mReceivers.decrementCurReceivers();
+
+ if (queue.runningOomAdjusted) {
+ mService.enqueueOomAdjTargetLocked(queue.app);
+ }
}
}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index d080036733a5..dec8b62de2ec 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -101,7 +101,7 @@ option java_package com.android.server.am
30073 uc_finish_user_stopping (userId|1|5)
30074 uc_finish_user_stopped (userId|1|5)
30075 uc_switch_user (userId|1|5)
-30076 uc_start_user_internal (userId|1|5)
+30076 uc_start_user_internal (userId|1|5),(foreground|1),(displayId|1|5)
30077 uc_unlock_user (userId|1|5)
30078 uc_finish_user_boot (userId|1|5)
30079 uc_dispatch_user_switch (oldUserId|1|5),(newUserId|1|5)
@@ -109,13 +109,14 @@ option java_package com.android.server.am
30081 uc_send_user_broadcast (userId|1|5),(IntentAction|3)
# Tags below are used by SystemServiceManager - although it's technically part of am, these are
# also user switch events and useful to be analyzed together with events above.
-30082 ssm_user_starting (userId|1|5)
+30082 ssm_user_starting (userId|1|5),(visible|1)
30083 ssm_user_switching (oldUserId|1|5),(newUserId|1|5)
30084 ssm_user_unlocking (userId|1|5)
30085 ssm_user_unlocked (userId|1|5)
-30086 ssm_user_stopping (userId|1|5)
+30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
30087 ssm_user_stopped (userId|1|5)
30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
+30089 ssm_user_visible (userId|1|5)
# Foreground service start/stop events.
30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 3b04dbb1da98..0a8c6400a6fd 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -54,6 +54,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.Zygote;
+import com.android.server.FgThread;
import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
@@ -143,6 +144,13 @@ class ProcessRecord implements WindowProcessListener {
private IApplicationThread mThread;
/**
+ * Instance of {@link #mThread} that will always meet the {@code oneway}
+ * contract, possibly by using {@link SameProcessApplicationThread}.
+ */
+ @CompositeRWLock({"mService", "mProcLock"})
+ private IApplicationThread mOnewayThread;
+
+ /**
* Always keep this application running?
*/
private volatile boolean mPersistent;
@@ -603,16 +611,27 @@ class ProcessRecord implements WindowProcessListener {
return mThread;
}
+ @GuardedBy(anyOf = {"mService", "mProcLock"})
+ IApplicationThread getOnewayThread() {
+ return mOnewayThread;
+ }
+
@GuardedBy({"mService", "mProcLock"})
public void makeActive(IApplicationThread thread, ProcessStatsService tracker) {
mProfile.onProcessActive(thread, tracker);
mThread = thread;
+ if (mPid == Process.myPid()) {
+ mOnewayThread = new SameProcessApplicationThread(thread, FgThread.getHandler());
+ } else {
+ mOnewayThread = thread;
+ }
mWindowProcessController.setThread(thread);
}
@GuardedBy({"mService", "mProcLock"})
public void makeInactive(ProcessStatsService tracker) {
mThread = null;
+ mOnewayThread = null;
mWindowProcessController.setThread(null);
mProfile.onProcessInactive(tracker);
}
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
new file mode 100644
index 000000000000..a3c011188539
--- /dev/null
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -0,0 +1,73 @@
+/*
+ * 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.am;
+
+import android.annotation.NonNull;
+import android.app.IApplicationThread;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+
+import java.util.Objects;
+
+/**
+ * Wrapper around an {@link IApplicationThread} that delegates selected calls
+ * through a {@link Handler} so they meet the {@code oneway} contract of
+ * returning immediately after dispatch.
+ */
+public class SameProcessApplicationThread extends IApplicationThread.Default {
+ private final IApplicationThread mWrapped;
+ private final Handler mHandler;
+
+ public SameProcessApplicationThread(@NonNull IApplicationThread wrapped,
+ @NonNull Handler handler) {
+ mWrapped = Objects.requireNonNull(wrapped);
+ mHandler = Objects.requireNonNull(handler);
+ }
+
+ @Override
+ public void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
+ int resultCode, String data, Bundle extras, boolean sync, int sendingUser,
+ int processState) {
+ mHandler.post(() -> {
+ try {
+ mWrapped.scheduleReceiver(intent, info, compatInfo, resultCode, data, extras, sync,
+ sendingUser, processState);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Override
+ public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode,
+ String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser,
+ int processState) {
+ mHandler.post(() -> {
+ try {
+ mWrapped.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras,
+ ordered, sticky, sendingUser, processState);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3c26116e8ad2..dcc7a8ea4e44 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -100,6 +100,7 @@ import android.util.EventLog;
import android.util.IntArray;
import android.util.Pair;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -174,6 +175,9 @@ class UserController implements Handler.Callback {
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
+ static final int USER_VISIBLE_MSG = 150;
+
+ private static final int NO_ARG2 = 0;
// Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
// the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -421,6 +425,17 @@ class UserController implements Handler.Callback {
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
+ /**
+ * List of visible users (as defined by {@link UserManager#isUserVisible()}).
+ *
+ * <p>It's only used to call {@link SystemServiceManager} when the visibility is changed upon
+ * the user starting or stopping.
+ *
+ * <p>Note: only the key is used, not the value.
+ */
+ @GuardedBy("mLock")
+ private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray();
+
UserController(ActivityManagerService service) {
this(new Injector(service));
}
@@ -1050,11 +1065,27 @@ class UserController implements Handler.Callback {
// instead.
userManagerInternal.unassignUserFromDisplay(userId);
+ final boolean visibilityChanged;
+ boolean visibleBefore;
+ synchronized (mLock) {
+ visibleBefore = mVisibleUsers.get(userId);
+ if (visibleBefore) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "Removing %d from mVisibleUsers", userId);
+ }
+ mVisibleUsers.delete(userId);
+ visibilityChanged = true;
+ } else {
+ visibilityChanged = false;
+ }
+ }
+
updateStartedUserArrayLU();
final boolean allowDelayedLockingCopied = allowDelayedLocking;
Runnable finishUserStoppingAsync = () ->
- mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied));
+ mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied,
+ visibilityChanged));
if (mInjector.getUserManager().isPreCreated(userId)) {
finishUserStoppingAsync.run();
@@ -1092,7 +1123,7 @@ class UserController implements Handler.Callback {
}
private void finishUserStopping(final int userId, final UserState uss,
- final boolean allowDelayedLocking) {
+ final boolean allowDelayedLocking, final boolean visibilityChanged) {
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
synchronized (mLock) {
if (uss.state != UserState.STATE_STOPPING) {
@@ -1109,7 +1140,7 @@ class UserController implements Handler.Callback {
mInjector.batteryStatsServiceNoteEvent(
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
Integer.toString(userId), userId);
- mInjector.getSystemServiceManager().onUserStopping(userId);
+ mInjector.getSystemServiceManager().onUserStopping(userId, visibilityChanged);
Runnable finishUserStoppedAsync = () ->
mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1513,16 +1544,17 @@ class UserController implements Handler.Callback {
private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
@Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
if (DEBUG_MU) {
- Slogf.i(TAG, "Starting user %d on display %d %s", userId, displayId,
+ Slogf.i(TAG, "Starting user %d on display %d%s", userId, displayId,
foreground ? " in foreground" : "");
}
- if (displayId != Display.DEFAULT_DISPLAY) {
+ boolean onSecondaryDisplay = displayId != Display.DEFAULT_DISPLAY;
+ if (onSecondaryDisplay) {
Preconditions.checkArgument(!foreground, "Cannot start user %d in foreground AND "
+ "on secondary display (%d)", userId, displayId);
}
- // TODO(b/239982558): log display id (or use a new event)
- EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId);
+ EventLog.writeEvent(EventLogTags.UC_START_USER_INTERNAL, userId, foreground ? 1 : 0,
+ displayId);
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -1571,8 +1603,9 @@ class UserController implements Handler.Callback {
return false;
}
- if (foreground && userInfo.preCreated) {
- Slogf.w(TAG, "Cannot start pre-created user #" + userId + " as foreground");
+ if ((foreground || onSecondaryDisplay) && userInfo.preCreated) {
+ Slogf.w(TAG, "Cannot start pre-created user #" + userId + " in foreground or on "
+ + "secondary display");
return false;
}
@@ -1656,6 +1689,28 @@ class UserController implements Handler.Callback {
}
t.traceEnd();
+ // Need to call UM when user is on background, as there are some cases where the user
+ // cannot be started in background on a secondary display (for example, if user is a
+ // profile).
+ // TODO(b/253103846): it's also explicitly checking if the user is the USER_SYSTEM, as
+ // the UM call would return true during boot (when CarService / BootUserInitializer
+ // calls AM.startUserInBackground() because the system user is still the current user.
+ // TODO(b/244644281): another fragility of this check is that it must wait to call
+ // UMI.isUserVisible() until the user state is check, as that method checks if the
+ // profile of the current user is started. We should fix that dependency so the logic
+ // belongs to just one place (like UserDisplayAssigner)
+ boolean visible = foreground
+ || userId != UserHandle.USER_SYSTEM
+ && mInjector.getUserManagerInternal().isUserVisible(userId);
+ if (visible) {
+ synchronized (mLock) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "Adding %d to mVisibleUsers", userId);
+ }
+ mVisibleUsers.put(userId, true);
+ }
+ }
+
// Make sure user is in the started state. If it is currently
// stopping, we need to knock that off.
if (uss.state == UserState.STATE_STOPPING) {
@@ -1692,8 +1747,15 @@ class UserController implements Handler.Callback {
// Booting up a new user, need to tell system services about it.
// Note that this is on the same handler as scheduling of broadcasts,
// which is important because it needs to go first.
- mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, 0));
+ mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
+ visible ? 1 : 0));
t.traceEnd();
+ } else if (visible) {
+ // User was already running and became visible (for example, when switching to a
+ // user that was started in the background before), so it's necessary to explicitly
+ // notify the services (while when the user starts from BOOTING, USER_START_MSG
+ // takes care of that.
+ mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBLE_MSG, userId, NO_ARG2));
}
t.traceBegin("sendMessages");
@@ -2110,6 +2172,11 @@ class UserController implements Handler.Callback {
mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
+ if (oldUserId == UserHandle.USER_SYSTEM) {
+ // System user is never stopped, but its visibility is changed (as it is brought to the
+ // background)
+ updateSystemUserVisibility(/* visible= */ false);
+ }
t.traceEnd(); // end continueUserSwitch
}
@@ -2413,9 +2480,7 @@ class UserController implements Handler.Callback {
void setAllowUserUnlocking(boolean allowed) {
mAllowUserUnlocking = allowed;
if (DEBUG_MU) {
- // TODO(b/245335748): use Slogf.d instead
- // Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed);
- android.util.Slog.d(TAG, "setAllowUserUnlocking():" + allowed, new Exception());
+ Slogf.d(TAG, new Exception(), "setAllowUserUnlocking(%b)", allowed);
}
}
@@ -2457,10 +2522,34 @@ class UserController implements Handler.Callback {
}
void onSystemReady() {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "onSystemReady()");
+
+ }
updateCurrentProfileIds();
mInjector.reportCurWakefulnessUsageEvent();
}
+ // TODO(b/242195409): remove this method if initial system user boot logic is refactored?
+ void onSystemUserStarting() {
+ updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
+ }
+
+ private void updateSystemUserVisibility(boolean visible) {
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
+ }
+ int userId = UserHandle.USER_SYSTEM;
+ synchronized (mLock) {
+ if (visible) {
+ mVisibleUsers.put(userId, true);
+ } else {
+ mVisibleUsers.delete(userId);
+ }
+ }
+ mInjector.onUserStarting(userId, visible);
+ }
+
/**
* Refreshes the list of users related to the current user when either a
* user switch happens or when a new related user is started in the
@@ -2846,6 +2935,9 @@ class UserController implements Handler.Callback {
proto.end(uToken);
}
}
+ for (int i = 0; i < mVisibleUsers.size(); i++) {
+ proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i));
+ }
proto.end(token);
}
}
@@ -2899,7 +2991,8 @@ class UserController implements Handler.Callback {
if (mSwitchingToSystemUserMessage != null) {
pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
}
- pw.println(" mLastUserUnlockingUptime:" + mLastUserUnlockingUptime);
+ pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
+ pw.println(" mVisibleUsers: " + mVisibleUsers);
}
}
@@ -2936,8 +3029,7 @@ class UserController implements Handler.Callback {
logUserLifecycleEvent(msg.arg1, USER_LIFECYCLE_EVENT_START_USER,
USER_LIFECYCLE_EVENT_STATE_BEGIN);
- mInjector.getSystemServiceManager().onUserStarting(
- TimingsTraceAndSlog.newAsyncLog(), msg.arg1);
+ mInjector.onUserStarting(/* userId= */ msg.arg1, /* visible= */ msg.arg2 == 1);
scheduleOnUserCompletedEvent(msg.arg1,
UserCompletedEventType.EVENT_TYPE_USER_STARTING,
USER_COMPLETED_EVENT_DELAY_MS);
@@ -3018,6 +3110,9 @@ class UserController implements Handler.Callback {
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1);
break;
+ case USER_VISIBLE_MSG:
+ mInjector.getSystemServiceManager().onUserVisible(/* userId= */ msg.arg1);
+ break;
}
return false;
}
@@ -3539,5 +3634,10 @@ class UserController implements Handler.Callback {
boolean isUsersOnSecondaryDisplaysEnabled() {
return UserManager.isUsersOnSecondaryDisplaysEnabled();
}
+
+ void onUserStarting(int userId, boolean visible) {
+ getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
+ visible);
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index c59ee83aa895..bbffc894ef3e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -344,7 +344,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
}
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"setCommunicationRouteForClient for pid: " + pid
+ " device: " + device
+ " from API: " + eventSource)).printLog(TAG));
@@ -1212,7 +1212,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (useCase == AudioSystem.FOR_MEDIA) {
postReportNewRoutes(fromA2dp);
}
- AudioService.sForceUseLogger.log(
+ AudioService.sForceUseLogger.enqueue(
new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_FORCE_USE + MediaMetrics.SEPARATOR
+ AudioSystem.forceUseUsageToString(useCase))
@@ -1230,7 +1230,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void onSendBecomingNoisyIntent() {
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
mSystemServer.sendDeviceBecomingNoisyIntent();
}
@@ -1468,7 +1468,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: {
final BtDeviceInfo info = (BtDeviceInfo) msg.obj;
if (info.mDevice == null) break;
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"msg: onBluetoothActiveDeviceChange "
+ " state=" + info.mState
// only querying address as this is the only readily available
@@ -1858,7 +1858,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
Log.v(TAG, "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource);
}
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"onUpdateCommunicationRoute, preferredCommunicationDevice: "
+ preferredCommunicationDevice + " eventSource: " + eventSource)));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ce36ff829693..c8f282fd576e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -309,7 +309,7 @@ public class AudioDeviceInventory {
address = "";
}
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent("BT connected:"
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent("BT connected:"
+ " addr=" + address
+ " profile=" + btInfo.mProfile
+ " state=" + btInfo.mState
@@ -412,13 +412,13 @@ public class AudioDeviceInventory {
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
address = "";
}
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"onBluetoothA2dpDeviceConfigChange addr=" + address
+ " event=" + BtHelper.a2dpDeviceEventToString(event)));
synchronized (mDevicesLock) {
if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2dp config change ignored (scheduled connection change)")
.printLog(TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
@@ -460,7 +460,7 @@ public class AudioDeviceInventory {
BtHelper.getName(btDevice), a2dpCodec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange failed for A2DP device addr=" + address
+ " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
.printLog(TAG));
@@ -472,7 +472,7 @@ public class AudioDeviceInventory {
BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
} else {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM handleDeviceConfigChange success for A2DP device addr=" + address
+ " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
.printLog(TAG));
@@ -522,7 +522,7 @@ public class AudioDeviceInventory {
AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
int type = wdcs.mAttributes.getInternalType();
- AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
+ AudioService.sDeviceLogger.enqueue(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+ "onSetWiredDeviceConnectionState")
@@ -619,7 +619,7 @@ public class AudioDeviceInventory {
@NonNull List<AudioDeviceAttributes> devices) {
final long identity = Binder.clearCallingIdentity();
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"setPreferredDevicesForStrategySync, strategy: " + strategy
+ " devices: " + devices)).printLog(TAG));
final int status = mAudioSystem.setDevicesRoleForStrategy(
@@ -635,7 +635,7 @@ public class AudioDeviceInventory {
/*package*/ int removePreferredDevicesForStrategySync(int strategy) {
final long identity = Binder.clearCallingIdentity();
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"removePreferredDevicesForStrategySync, strategy: "
+ strategy)).printLog(TAG));
@@ -1000,13 +1000,13 @@ public class AudioDeviceInventory {
// TODO: log in MediaMetrics once distinction between connection failure and
// double connection is made.
if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make available A2DP device addr=" + address
+ " error=" + res).printLog(TAG));
// TODO: connection failed, stop here
// TODO: return;
} else {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"A2DP device addr=" + address + " now available").printLog(TAG));
}
@@ -1047,7 +1047,7 @@ public class AudioDeviceInventory {
if (!deviceToRemoveKey
.equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
// removing A2DP device not currently used by AudioPolicy, log but don't act on it
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN,
"A2DP device made unavailable, was not used")
@@ -1062,13 +1062,13 @@ public class AudioDeviceInventory {
AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"APM failed to make unavailable A2DP device addr=" + address
+ " error=" + res).printLog(TAG));
// TODO: failed to disconnect, stop here
// TODO: return;
} else {
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"A2DP device addr=" + address + " made unavailable")).printLog(TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -1314,7 +1314,7 @@ public class AudioDeviceInventory {
&& !mDeviceBroker.hasAudioFocusUsers()) {
// no media playback, not a "becoming noisy" situation, otherwise it could cause
// the pausing of some apps that are playing remotely
- AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
"dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
return 0;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cbfd17f0a418..9d6fa9ec06bf 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -990,7 +990,7 @@ public class AudioService extends IAudioService.Stub
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
AppOpsManager appOps) {
- sLifecycleLogger.log(new EventLogger.StringEvent("AudioService()"));
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
mContentResolver = context.getContentResolver();
mAppOps = appOps;
@@ -1539,14 +1539,14 @@ public class AudioService extends IAudioService.Stub
if (!mSystemReady ||
(AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) {
Log.e(TAG, "Audioserver died.");
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
"onAudioServerDied() audioserver died"));
sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED, SENDMSG_NOOP, 0, 0,
null, 500);
return;
}
Log.i(TAG, "Audioserver started.");
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
"onAudioServerDied() audioserver started"));
updateAudioHalPids();
@@ -1776,7 +1776,7 @@ public class AudioService extends IAudioService.Stub
// did it work? check based on status
if (status != AudioSystem.AUDIO_STATUS_OK) {
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
caller + ": initStreamVolume failed with " + status + " will retry")
.printLog(ALOGE, TAG));
sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -1790,7 +1790,7 @@ public class AudioService extends IAudioService.Stub
}
// success
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
caller + ": initStreamVolume succeeded").printLog(ALOGI, TAG));
}
@@ -1813,7 +1813,7 @@ public class AudioService extends IAudioService.Stub
}
}
if (!success) {
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
caller + ": initStreamVolume succeeded but invalid mix/max levels, will retry")
.printLog(ALOGW, TAG));
sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -2764,7 +2764,7 @@ public class AudioService extends IAudioService.Stub
"setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
Binder.getCallingUid(), Binder.getCallingPid(), strategy,
devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
if (devices.stream().anyMatch(device ->
device.getRole() == AudioDeviceAttributes.ROLE_INPUT)) {
Log.e(TAG, "Unsupported input routing in " + logString);
@@ -2784,7 +2784,7 @@ public class AudioService extends IAudioService.Stub
public int removePreferredDevicesForStrategy(int strategy) {
final String logString =
String.format("removePreferredDeviceForStrategy strat:%d", strategy);
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy);
if (status != AudioSystem.SUCCESS) {
@@ -2850,7 +2850,7 @@ public class AudioService extends IAudioService.Stub
"setPreferredDevicesForCapturePreset u/pid:%d/%d source:%d dev:%s",
Binder.getCallingUid(), Binder.getCallingPid(), capturePreset,
devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
if (devices.stream().anyMatch(device ->
device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT)) {
Log.e(TAG, "Unsupported output routing in " + logString);
@@ -2871,7 +2871,7 @@ public class AudioService extends IAudioService.Stub
public int clearPreferredDevicesForCapturePreset(int capturePreset) {
final String logString = String.format(
"removePreferredDeviceForCapturePreset source:%d", capturePreset);
- sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset);
if (status != AudioSystem.SUCCESS) {
@@ -3043,9 +3043,10 @@ public class AudioService extends IAudioService.Stub
+ ", volControlStream=" + mVolumeControlStream
+ ", userSelect=" + mUserSelectedVolumeControlStream);
if (direction != AudioManager.ADJUST_SAME) {
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
- direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
- .append("/").append(caller).append(" uid:").append(uid).toString()));
+ sVolumeLogger.enqueue(
+ new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
+ direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
+ .append("/").append(caller).append(" uid:").append(uid).toString()));
}
boolean hasExternalVolumeController = notifyExternalVolumeController(direction);
@@ -3144,7 +3145,7 @@ public class AudioService extends IAudioService.Stub
return;
}
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
@@ -3625,7 +3626,7 @@ public class AudioService extends IAudioService.Stub
}
final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
index/*val1*/, flags/*val2*/, callingPackage));
vgs.setVolumeIndex(index, flags);
@@ -3776,7 +3777,7 @@ public class AudioService extends IAudioService.Stub
? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
index/*val1*/, flags/*val2*/, callingPackage)
: new DeviceVolumeEvent(streamType, index, device, callingPackage);
- sVolumeLogger.log(event);
+ sVolumeLogger.enqueue(event);
setStreamVolume(streamType, index, flags, device,
callingPackage, callingPackage, attributionTag,
Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
@@ -3977,7 +3978,7 @@ public class AudioService extends IAudioService.Stub
private void updateHearingAidVolumeOnVoiceActivityUpdate() {
final int streamType = getBluetoothContextualVolumeStream();
final int index = getStreamVolume(streamType);
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
mVoicePlaybackActive.get(), streamType, index));
mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
@@ -4018,13 +4019,13 @@ public class AudioService extends IAudioService.Stub
if (AudioSystem.isSingleAudioDeviceType(
absVolumeMultiModeCaseDevices, AudioSystem.DEVICE_OUT_HEARING_AID)) {
final int index = getStreamVolume(streamType);
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID,
newMode, streamType, index));
mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
}
}
- private void setLeAudioVolumeOnModeUpdate(int mode, int streamType, int device) {
+ private void setLeAudioVolumeOnModeUpdate(int mode, int device) {
switch (mode) {
case AudioSystem.MODE_IN_COMMUNICATION:
case AudioSystem.MODE_IN_CALL:
@@ -4038,10 +4039,16 @@ public class AudioService extends IAudioService.Stub
return;
}
- // Currently, DEVICE_OUT_BLE_HEADSET is the only output type for LE_AUDIO profile.
- // (See AudioDeviceBroker#createBtDeviceInfo())
- int index = mStreamStates[streamType].getIndex(AudioSystem.DEVICE_OUT_BLE_HEADSET);
- int maxIndex = mStreamStates[streamType].getMaxIndex();
+ // Forcefully set LE audio volume as a workaround, since in some cases
+ // (like the outgoing call) the value of 'device' is not DEVICE_OUT_BLE_*
+ // even when BLE is connected.
+ if (!AudioSystem.isLeAudioDeviceType(device)) {
+ device = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+ }
+
+ final int streamType = getBluetoothContextualVolumeStream(mode);
+ final int index = mStreamStates[streamType].getIndex(device);
+ final int maxIndex = mStreamStates[streamType].getMaxIndex();
if (DEBUG_VOL) {
Log.d(TAG, "setLeAudioVolumeOnModeUpdate postSetLeAudioVolumeIndex index="
@@ -5413,7 +5420,7 @@ public class AudioService extends IAudioService.Stub
/*obj*/ null, /*delay*/ 0);
int previousMode = mMode.getAndSet(mode);
// Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
- mModeLogger.log(new PhoneStateEvent(requesterPackage, requesterPid,
+ mModeLogger.enqueue(new PhoneStateEvent(requesterPackage, requesterPid,
requestedMode, pid, mode));
int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
@@ -5427,9 +5434,7 @@ public class AudioService extends IAudioService.Stub
// change of mode may require volume to be re-applied on some devices
updateAbsVolumeMultiModeDevices(previousMode, mode);
- // Forcefully set LE audio volume as a workaround, since the value of 'device'
- // is not DEVICE_OUT_BLE_* even when BLE is connected.
- setLeAudioVolumeOnModeUpdate(mode, streamType, device);
+ setLeAudioVolumeOnModeUpdate(mode, device);
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
@@ -5558,7 +5563,7 @@ public class AudioService extends IAudioService.Stub
}
if (direction != AudioManager.ADJUST_SAME) {
- sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
+ sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
direction/*val1*/, flags/*val2*/,
new StringBuilder(packageName).append(" uid:").append(uid)
.toString()));
@@ -6888,7 +6893,7 @@ public class AudioService extends IAudioService.Stub
// verify arguments
Objects.requireNonNull(device);
AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
- sVolumeLogger.log(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+ sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+ AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
+ device.getAddress() + " behavior:"
+ AudioDeviceVolumeManager.volumeBehaviorName(deviceVolumeBehavior)
@@ -6944,7 +6949,7 @@ public class AudioService extends IAudioService.Stub
}
// log event and caller
- sDeviceLogger.log(new EventLogger.StringEvent(
+ sDeviceLogger.enqueue(new EventLogger.StringEvent(
"Volume behavior " + deviceVolumeBehavior + " for dev=0x"
+ Integer.toHexString(audioSystemDeviceOut) + " from:" + caller));
// make sure we have a volume entry for this device, and that volume is updated according
@@ -7590,7 +7595,7 @@ public class AudioService extends IAudioService.Stub
final int status = AudioSystem.initStreamVolume(
streamType, mIndexMin / 10, mIndexMax / 10);
if (status != AudioSystem.AUDIO_STATUS_OK) {
- sLifecycleLogger.log(new EventLogger.StringEvent(
+ sLifecycleLogger.enqueue(new EventLogger.StringEvent(
"VSS() stream:" + streamType + " initStreamVolume=" + status)
.printLog(ALOGE, TAG));
sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -8009,7 +8014,7 @@ public class AudioService extends IAudioService.Stub
}
}
if (changed) {
- sVolumeLogger.log(new VolumeEvent(
+ sVolumeLogger.enqueue(new VolumeEvent(
VolumeEvent.VOL_MUTE_STREAM_INT, mStreamType, state));
}
return changed;
@@ -8179,10 +8184,10 @@ public class AudioService extends IAudioService.Stub
streamState.setIndex(index, update.mDevice, update.mCaller,
// trusted as index is always validated before message is posted
true /*hasModifyAudioSettings*/);
- sVolumeLogger.log(new EventLogger.StringEvent(update.mCaller + " dev:0x"
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(update.mCaller + " dev:0x"
+ Integer.toHexString(update.mDevice) + " volIdx:" + index));
} else {
- sVolumeLogger.log(new EventLogger.StringEvent(update.mCaller
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(update.mCaller
+ " update vol on dev:0x" + Integer.toHexString(update.mDevice)));
}
setDeviceVolume(streamState, update.mDevice);
@@ -8360,7 +8365,7 @@ public class AudioService extends IAudioService.Stub
.set(MediaMetrics.Property.FORCE_USE_MODE,
AudioSystem.forceUseConfigToString(config))
.record();
- sForceUseLogger.log(
+ sForceUseLogger.enqueue(
new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
mAudioSystem.setForceUse(useCase, config);
}
@@ -8629,7 +8634,7 @@ public class AudioService extends IAudioService.Stub
private void avrcpSupportsAbsoluteVolume(String address, boolean support) {
// address is not used for now, but may be used when multiple a2dp devices are supported
- sVolumeLogger.log(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
+ sVolumeLogger.enqueue(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
+ address + " support=" + support).printLog(TAG));
mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
setAvrcpAbsoluteVolumeSupported(support);
@@ -8763,13 +8768,21 @@ public class AudioService extends IAudioService.Stub
UserInfo userInfo = UserManagerService.getInstance().getUserInfo(userId);
killBackgroundUserProcessesWithRecordAudioPermission(userInfo);
}
- UserManagerService.getInstance().setUserRestriction(
- UserManager.DISALLOW_RECORD_AUDIO, true, userId);
+ try {
+ UserManagerService.getInstance().setUserRestriction(
+ UserManager.DISALLOW_RECORD_AUDIO, true, userId);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
+ }
} else if (action.equals(Intent.ACTION_USER_FOREGROUND)) {
// Enable audio recording for foreground user/profile
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- UserManagerService.getInstance().setUserRestriction(
- UserManager.DISALLOW_RECORD_AUDIO, false, userId);
+ try {
+ UserManagerService.getInstance().setUserRestriction(
+ UserManager.DISALLOW_RECORD_AUDIO, false, userId);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
+ }
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
if (state == BluetoothAdapter.STATE_OFF ||
@@ -10664,7 +10677,7 @@ public class AudioService extends IAudioService.Stub
pcb.asBinder().linkToDeath(app, 0/*flags*/);
// logging after registration so we have the registration id
- mDynPolicyLogger.log((new EventLogger.StringEvent("registerAudioPolicy for "
+ mDynPolicyLogger.enqueue((new EventLogger.StringEvent("registerAudioPolicy for "
+ pcb.asBinder() + " u/pid:" + Binder.getCallingUid() + "/"
+ Binder.getCallingPid() + " with config:" + app.toCompactLogString()))
.printLog(TAG));
@@ -10862,7 +10875,7 @@ public class AudioService extends IAudioService.Stub
private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb, String operationName) {
- mDynPolicyLogger.log((new EventLogger.StringEvent(operationName + " for "
+ mDynPolicyLogger.enqueue((new EventLogger.StringEvent(operationName + " for "
+ pcb.asBinder()).printLog(TAG)));
synchronized (mAudioPolicies) {
AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
@@ -11390,7 +11403,7 @@ public class AudioService extends IAudioService.Stub
}
public void binderDied() {
- mDynPolicyLogger.log((new EventLogger.StringEvent("AudioPolicy "
+ mDynPolicyLogger.enqueue((new EventLogger.StringEvent("AudioPolicy "
+ mPolicyCallback.asBinder() + " died").printLog(TAG)));
release();
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 399829e32588..df65dbd53e06 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -263,20 +263,20 @@ public class BtHelper {
/*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) {
if (mA2dp == null) {
if (AudioService.DEBUG_VOL) {
- AudioService.sVolumeLogger.log(new EventLogger.StringEvent(
+ AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
"setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp").printLog(TAG));
return;
}
}
if (!mAvrcpAbsVolSupported) {
- AudioService.sVolumeLogger.log(new EventLogger.StringEvent(
+ AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
"setAvrcpAbsoluteVolumeIndex: abs vol not supported ").printLog(TAG));
return;
}
if (AudioService.DEBUG_VOL) {
Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
}
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index));
mA2dp.setAvrcpAbsoluteVolume(index);
}
@@ -393,14 +393,14 @@ public class BtHelper {
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
@NonNull String eventSource) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(eventSource));
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
}
// @GuardedBy("AudioDeviceBroker.mSetModeLock")
@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
/*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(eventSource));
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
}
@@ -418,7 +418,7 @@ public class BtHelper {
Log.i(TAG, "setLeAudioVolume: calling mLeAudio.setVolume idx="
+ index + " volume=" + volume);
}
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
mLeAudio.setVolume(volume);
}
@@ -443,7 +443,7 @@ public class BtHelper {
}
// do not log when hearing aid is not connected to avoid confusion when reading dumpsys
if (isHeadAidConnected) {
- AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+ AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
}
mHearingAid.setVolume(gainDB);
@@ -675,7 +675,7 @@ public class BtHelper {
case BluetoothProfile.HEADSET:
case BluetoothProfile.HEARING_AID:
case BluetoothProfile.LE_AUDIO:
- AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"BT profile service: connecting "
+ BluetoothProfile.getProfileName(profile) + " profile"));
mDeviceBroker.postBtProfileConnected(profile, proxy);
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index e54ee869fef2..5f6f4b125710 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -245,7 +245,7 @@ public final class FadeOutManager {
return;
}
try {
- PlaybackActivityMonitor.sEventLogger.log(
+ PlaybackActivityMonitor.sEventLogger.enqueue(
(new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
FADEOUT_VSHAPE,
@@ -262,7 +262,7 @@ public final class FadeOutManager {
final AudioPlaybackConfiguration apc = players.get(piid);
if (apc != null) {
try {
- PlaybackActivityMonitor.sEventLogger.log(
+ PlaybackActivityMonitor.sEventLogger.enqueue(
(new EventLogger.StringEvent("unfading out piid:"
+ piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 1ca27dd7112c..27687b2612e5 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -185,7 +185,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
final FocusRequester focusOwner = stackIterator.next();
if (focusOwner.hasSameUid(uid) && focusOwner.hasSamePackage(packageName)) {
clientsToRemove.add(focusOwner.getClientId());
- mEventLogger.log((new EventLogger.StringEvent(
+ mEventLogger.enqueue((new EventLogger.StringEvent(
"focus owner:" + focusOwner.getClientId()
+ " in uid:" + uid + " pack: " + packageName
+ " getting AUDIOFOCUS_LOSS due to app suspension"))
@@ -433,7 +433,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
FocusRequester fr = stackIterator.next();
if(fr.hasSameBinder(cb)) {
Log.i(TAG, "AudioFocus removeFocusStackEntryOnDeath(): removing entry for " + cb);
- mEventLogger.log(new EventLogger.StringEvent(
+ mEventLogger.enqueue(new EventLogger.StringEvent(
"focus requester:" + fr.getClientId()
+ " in uid:" + fr.getClientUid()
+ " pack:" + fr.getPackageName()
@@ -470,7 +470,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
final FocusRequester fr = owner.getValue();
if (fr.hasSameBinder(cb)) {
ownerIterator.remove();
- mEventLogger.log(new EventLogger.StringEvent(
+ mEventLogger.enqueue(new EventLogger.StringEvent(
"focus requester:" + fr.getClientId()
+ " in uid:" + fr.getClientUid()
+ " pack:" + fr.getPackageName()
@@ -968,7 +968,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
// supposed to be alone in bitfield
final int uid = (flags == AudioManager.AUDIOFOCUS_FLAG_TEST)
? testUid : Binder.getCallingUid();
- mEventLogger.log((new EventLogger.StringEvent(
+ mEventLogger.enqueue((new EventLogger.StringEvent(
"requestAudioFocus() from uid/pid " + uid
+ "/" + Binder.getCallingPid()
+ " AA=" + aa.usageToString() + "/" + aa.contentTypeToString()
@@ -1143,7 +1143,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
.record();
// AudioAttributes are currently ignored, to be used for zones / a11y
- mEventLogger.log((new EventLogger.StringEvent(
+ mEventLogger.enqueue((new EventLogger.StringEvent(
"abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+ "/" + Binder.getCallingPid()
+ " clientId=" + clientId))
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 1af8c593f96b..74bfa80e4704 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -157,7 +157,7 @@ public final class PlaybackActivityMonitor
if (index >= 0) {
if (!disable) {
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- sEventLogger.log(new EventLogger.StringEvent("unbanning uid:" + uid));
+ sEventLogger.enqueue(new EventLogger.StringEvent("unbanning uid:" + uid));
}
mBannedUids.remove(index);
// nothing else to do, future playback requests from this uid are ok
@@ -168,7 +168,7 @@ public final class PlaybackActivityMonitor
checkBanPlayer(apc, uid);
}
if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
- sEventLogger.log(new EventLogger.StringEvent("banning uid:" + uid));
+ sEventLogger.enqueue(new EventLogger.StringEvent("banning uid:" + uid));
}
mBannedUids.add(new Integer(uid));
} // no else to handle, uid already not in list, so enabling again is no-op
@@ -209,7 +209,7 @@ public final class PlaybackActivityMonitor
updateAllowedCapturePolicy(apc, mAllowedCapturePolicies.get(uid));
}
}
- sEventLogger.log(new NewPlayerEvent(apc));
+ sEventLogger.enqueue(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
maybeMutePlayerAwaitingConnection(apc);
@@ -229,7 +229,7 @@ public final class PlaybackActivityMonitor
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- sEventLogger.log(new AudioAttrEvent(piid, attr));
+ sEventLogger.enqueue(new AudioAttrEvent(piid, attr));
change = apc.handleAudioAttributesEvent(attr);
} else {
Log.e(TAG, "Error updating audio attributes");
@@ -322,7 +322,7 @@ public final class PlaybackActivityMonitor
return;
}
- sEventLogger.log(new PlayerEvent(piid, event, eventValue));
+ sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
mEventHandler.sendMessage(
@@ -332,7 +332,7 @@ public final class PlaybackActivityMonitor
for (Integer uidInteger: mBannedUids) {
if (checkBanPlayer(apc, uidInteger.intValue())) {
// player was banned, do not update its state
- sEventLogger.log(new EventLogger.StringEvent(
+ sEventLogger.enqueue(new EventLogger.StringEvent(
"not starting piid:" + piid + " ,is banned"));
return;
}
@@ -412,7 +412,7 @@ public final class PlaybackActivityMonitor
public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
// no check on UID yet because this is only for logging at the moment
- sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+ sEventLogger.enqueue(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
}
public void releasePlayer(int piid, int binderUid) {
@@ -421,7 +421,7 @@ public final class PlaybackActivityMonitor
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (checkConfigurationCaller(piid, apc, binderUid)) {
- sEventLogger.log(new EventLogger.StringEvent(
+ sEventLogger.enqueue(new EventLogger.StringEvent(
"releasing player piid:" + piid));
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
@@ -443,7 +443,7 @@ public final class PlaybackActivityMonitor
}
/*package*/ void onAudioServerDied() {
- sEventLogger.log(
+ sEventLogger.enqueue(
new EventLogger.StringEvent(
"clear port id to piid map"));
synchronized (mPlayerLock) {
@@ -768,7 +768,7 @@ public final class PlaybackActivityMonitor
}
if (mute) {
try {
- sEventLogger.log((new EventLogger.StringEvent("call: muting piid:"
+ sEventLogger.enqueue((new EventLogger.StringEvent("call: muting piid:"
+ piid + " uid:" + apc.getClientUid())).printLog(TAG));
apc.getPlayerProxy().setVolume(0.0f);
mMutedPlayers.add(new Integer(piid));
@@ -793,7 +793,7 @@ public final class PlaybackActivityMonitor
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
if (apc != null) {
try {
- sEventLogger.log(new EventLogger.StringEvent("call: unmuting piid:"
+ sEventLogger.enqueue(new EventLogger.StringEvent("call: unmuting piid:"
+ piid).printLog(TAG));
apc.getPlayerProxy().setVolume(1.0f);
} catch (Exception e) {
@@ -1081,7 +1081,7 @@ public final class PlaybackActivityMonitor
return;
}
try {
- sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
+ sEventLogger.enqueue((new DuckEvent(apc, skipRamp)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_VSHAPE,
skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -1096,7 +1096,7 @@ public final class PlaybackActivityMonitor
final AudioPlaybackConfiguration apc = players.get(piid);
if (apc != null) {
try {
- sEventLogger.log((new EventLogger.StringEvent("unducking piid:"
+ sEventLogger.enqueue((new EventLogger.StringEvent("unducking piid:"
+ piid)).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(
DUCK_ID,
@@ -1310,8 +1310,9 @@ public final class PlaybackActivityMonitor
//==========================================================================================
void muteAwaitConnection(@NonNull int[] usagesToMute,
@NonNull AudioDeviceAttributes dev, long timeOutMs) {
- sEventLogger.loglogi(
- "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs, TAG);
+ sEventLogger.enqueueAndLog(
+ "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs,
+ EventLogger.Event.ALOGI, TAG);
synchronized (mPlayerLock) {
mutePlayersExpectingDevice(usagesToMute);
// schedule timeout (remove previously scheduled first)
@@ -1323,7 +1324,8 @@ public final class PlaybackActivityMonitor
}
void cancelMuteAwaitConnection(String source) {
- sEventLogger.loglogi("cancelMuteAwaitConnection() from:" + source, TAG);
+ sEventLogger.enqueueAndLog("cancelMuteAwaitConnection() from:" + source,
+ EventLogger.Event.ALOGI, TAG);
synchronized (mPlayerLock) {
// cancel scheduled timeout, ignore device, only one expected device at a time
mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
@@ -1346,7 +1348,7 @@ public final class PlaybackActivityMonitor
@GuardedBy("mPlayerLock")
private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
- sEventLogger.log(new MuteAwaitConnectionEvent(usagesToMute));
+ sEventLogger.enqueue(new MuteAwaitConnectionEvent(usagesToMute));
mMutedUsagesAwaitingConnection = usagesToMute;
final Set<Integer> piidSet = mPlayers.keySet();
final Iterator<Integer> piidIterator = piidSet.iterator();
@@ -1369,7 +1371,7 @@ public final class PlaybackActivityMonitor
for (int usage : mMutedUsagesAwaitingConnection) {
if (usage == apc.getAudioAttributes().getUsage()) {
try {
- sEventLogger.log((new EventLogger.StringEvent(
+ sEventLogger.enqueue((new EventLogger.StringEvent(
"awaiting connection: muting piid:"
+ apc.getPlayerInterfaceId()
+ " uid:" + apc.getClientUid())).printLog(TAG));
@@ -1394,7 +1396,7 @@ public final class PlaybackActivityMonitor
continue;
}
try {
- sEventLogger.log(new EventLogger.StringEvent(
+ sEventLogger.enqueue(new EventLogger.StringEvent(
"unmuting piid:" + piid).printLog(TAG));
apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
VolumeShaper.Operation.REVERSE);
@@ -1452,8 +1454,9 @@ public final class PlaybackActivityMonitor
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
- sEventLogger.loglogi("Timeout for muting waiting for "
- + (AudioDeviceAttributes) msg.obj + ", unmuting", TAG);
+ sEventLogger.enqueueAndLog("Timeout for muting waiting for "
+ + (AudioDeviceAttributes) msg.obj + ", unmuting",
+ EventLogger.Event.ALOGI, TAG);
synchronized (mPlayerLock) {
unmutePlayersExpectingDevice();
}
@@ -1476,7 +1479,7 @@ public final class PlaybackActivityMonitor
synchronized (mPlayerLock) {
int piid = msg.arg1;
- sEventLogger.log(
+ sEventLogger.enqueue(
new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
final AudioPlaybackConfiguration apc = mPlayers.get(piid);
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 2ba8882ae14f..652ea5228571 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -164,7 +164,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin
}
if (MediaRecorder.isSystemOnlyAudioSource(source)) {
// still want to log event, it just won't appear in recording configurations;
- sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG));
+ sEventLogger.enqueue(new RecordingEvent(event, riid, config).printLog(TAG));
return;
}
dispatchCallbacks(updateSnapshot(event, riid, config));
@@ -204,7 +204,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin
? AudioManager.RECORD_CONFIG_EVENT_STOP : AudioManager.RECORD_CONFIG_EVENT_NONE;
if (riid == AudioManager.RECORD_RIID_INVALID
|| configEvent == AudioManager.RECORD_CONFIG_EVENT_NONE) {
- sEventLogger.log(new RecordingEvent(event, riid, null).printLog(TAG));
+ sEventLogger.enqueue(new RecordingEvent(event, riid, null).printLog(TAG));
return;
}
dispatchCallbacks(updateSnapshot(configEvent, riid, null));
@@ -301,7 +301,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin
if (!state.hasDeathHandler()) {
if (state.isActiveConfiguration()) {
configChanged = true;
- sEventLogger.log(new RecordingEvent(
+ sEventLogger.enqueue(new RecordingEvent(
AudioManager.RECORD_CONFIG_EVENT_RELEASE,
state.getRiid(), state.getConfig()));
}
@@ -486,7 +486,7 @@ public final class RecordingActivityMonitor implements AudioSystem.AudioRecordin
configChanged = false;
}
if (configChanged) {
- sEventLogger.log(new RecordingEvent(event, riid, state.getConfig()));
+ sEventLogger.enqueue(new RecordingEvent(event, riid, state.getConfig()));
configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
}
}
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 93eba50ac6dd..79b54ebfeb3c 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -164,7 +164,7 @@ class SoundEffectsHelper {
}
private void logEvent(String msg) {
- mSfxLogger.log(new EventLogger.StringEvent(msg));
+ mSfxLogger.enqueue(new EventLogger.StringEvent(msg));
}
// All the methods below run on the worker thread
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 1563d33d93f0..2b525f1fcf50 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1708,11 +1708,11 @@ public class SpatializerHelper {
private static void loglogi(String msg) {
- AudioService.sSpatialLogger.loglogi(msg, TAG);
+ AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGI, TAG);
}
private static String logloge(String msg) {
- AudioService.sSpatialLogger.loglog(msg, EventLogger.Event.ALOGE, TAG);
+ AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGE, TAG);
return msg;
}
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index 1a5f31c8ac90..da4361843681 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -52,16 +52,13 @@ final class ALSProbe implements Probe {
private boolean mDestroyed = false;
private boolean mDestroyRequested = false;
private boolean mDisableRequested = false;
- private volatile NextConsumer mNextConsumer = null;
+ private NextConsumer mNextConsumer = null;
private volatile float mLastAmbientLux = -1;
private final SensorEventListener mLightSensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
- mLastAmbientLux = event.values[0];
- if (mNextConsumer != null) {
- completeNextConsumer(mLastAmbientLux);
- }
+ onNext(event.values[0]);
}
@Override
@@ -133,11 +130,29 @@ final class ALSProbe implements Probe {
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disable();
+ disableLightSensorLoggingLocked();
mDestroyed = true;
}
}
+ private synchronized void onNext(float value) {
+ mLastAmbientLux = value;
+
+ final NextConsumer consumer = mNextConsumer;
+ mNextConsumer = null;
+ if (consumer != null) {
+ Slog.v(TAG, "Finishing next consumer");
+
+ if (mDestroyRequested) {
+ destroy();
+ } else if (mDisableRequested) {
+ disable();
+ }
+
+ consumer.consume(value);
+ }
+ }
+
/** The most recent lux reading. */
public float getMostRecentLux() {
return mLastAmbientLux;
@@ -160,7 +175,7 @@ final class ALSProbe implements Probe {
@Nullable Handler handler) {
final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
final float current = mLastAmbientLux;
- if (current > 0) {
+ if (current > -1f) {
nextConsumer.consume(current);
} else if (mDestroyed) {
nextConsumer.consume(-1f);
@@ -172,23 +187,6 @@ final class ALSProbe implements Probe {
}
}
- private synchronized void completeNextConsumer(float value) {
- Slog.v(TAG, "Finishing next consumer");
-
- final NextConsumer consumer = mNextConsumer;
- mNextConsumer = null;
-
- if (mDestroyRequested) {
- destroy();
- } else if (mDisableRequested) {
- disable();
- }
-
- if (consumer != null) {
- consumer.consume(value);
- }
- }
-
private void enableLightSensorLoggingLocked() {
if (!mEnabled) {
mEnabled = true;
@@ -219,9 +217,13 @@ final class ALSProbe implements Probe {
}
}
- private void onTimeout() {
+ private synchronized void onTimeout() {
Slog.e(TAG, "Max time exceeded for ALS logger - disabling: "
+ mLightSensorListener.hashCode());
+
+ // if consumers are waiting but there was no sensor change, complete them with the latest
+ // value before disabling
+ onNext(mLastAmbientLux);
disable();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index aeb6b6e2a907..969a174f49c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.os.RemoteException;
@@ -43,6 +44,7 @@ public final class SensorOverlays {
@NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
@NonNull private final Optional<ISidefpsController> mSidefpsController;
+ @NonNull private final Optional<IUdfpsOverlay> mUdfpsOverlay;
/**
* Create an overlay controller for each modality.
@@ -52,9 +54,11 @@ public final class SensorOverlays {
*/
public SensorOverlays(
@Nullable IUdfpsOverlayController udfpsOverlayController,
- @Nullable ISidefpsController sidefpsController) {
+ @Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay) {
mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
mSidefpsController = Optional.ofNullable(sidefpsController);
+ mUdfpsOverlay = Optional.ofNullable(udfpsOverlay);
}
/**
@@ -90,6 +94,14 @@ public final class SensorOverlays {
Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
}
}
+
+ if (mUdfpsOverlay.isPresent()) {
+ try {
+ mUdfpsOverlay.get().show(client.getRequestId(), sensorId, reason);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when showing the new UDFPS overlay", e);
+ }
+ }
}
/**
@@ -113,6 +125,14 @@ public final class SensorOverlays {
Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
}
}
+
+ if (mUdfpsOverlay.isPresent()) {
+ try {
+ mUdfpsOverlay.get().hide(sensorId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception when hiding the new udfps overlay", e);
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 2761ec04aa7e..7a5b58413014 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
@@ -600,8 +600,9 @@ public class FaceService extends SystemService {
}
try {
final SensorProps[] props = face.getSensorProps();
- final FaceProvider provider = new FaceProvider(getContext(), props, instance,
- mLockoutResetDispatcher, BiometricContext.getInstance(getContext()));
+ final FaceProvider provider = new FaceProvider(getContext(),
+ mBiometricStateCallback, props, instance, mLockoutResetDispatcher,
+ BiometricContext.getInstance(getContext()));
providers.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -612,14 +613,14 @@ public class FaceService extends SystemService {
}
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
- @Override // Binder call
public void registerAuthenticators(
@NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
mRegistry.registerAll(() -> {
final List<ServiceProvider> providers = new ArrayList<>();
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
providers.add(
- Face10.newInstance(getContext(), hidlSensor, mLockoutResetDispatcher));
+ Face10.newInstance(getContext(), mBiometricStateCallback,
+ hidlSensor, mLockoutResetDispatcher));
}
providers.addAll(getAidlProviders());
return providers;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 73c272f7a779..cfbb5dce4c2b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -16,8 +16,6 @@
package com.android.server.biometrics.sensors.face.aidl;
-import static android.Manifest.permission.TEST_BIOMETRIC;
-
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.ITestSession;
@@ -33,7 +31,6 @@ import android.os.RemoteException;
import android.util.Slog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.face.FaceUtils;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index c12994c993e6..6488185c727d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -52,9 +52,11 @@ import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.InvalidationRequesterClient;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.PerformanceTracker;
@@ -81,6 +83,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
private boolean mTestHalEnabled;
@NonNull private final Context mContext;
+ @NonNull private final BiometricStateCallback mBiometricStateCallback;
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -122,11 +125,14 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
}
}
- public FaceProvider(@NonNull Context context, @NonNull SensorProps[] props,
+ public FaceProvider(@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext) {
mContext = context;
+ mBiometricStateCallback = biometricStateCallback;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
mHandler = new Handler(Looper.getMainLooper());
@@ -363,16 +369,18 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, maxTemplatesPerUser, debugConsent);
- scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
- });
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
+ }
+ }
+ }));
});
return id;
}
@@ -396,7 +404,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
token, id, callback, userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric);
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
return id;
@@ -424,7 +432,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mBiometricContext, isStrongBiometric,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, isKeyguardBypassEnabled, biometricStrength);
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -479,7 +487,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -568,7 +576,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
if (favorHalEnrollments) {
client.setFavorHalEnrollments();
}
- scheduleForSensor(sensorId, client, callback);
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(callback,
+ mBiometricStateCallback));
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
index 14af216a9dc5..7a6a274f8dd7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java
@@ -16,8 +16,6 @@
package com.android.server.biometrics.sensors.face.hidl;
-import static android.Manifest.permission.TEST_BIOMETRIC;
-
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.ITestSession;
@@ -30,7 +28,6 @@ import android.os.Binder;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.face.FaceUtils;
@@ -53,6 +50,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
@NonNull private final Set<Integer> mEnrollmentIds;
@NonNull private final Random mRandom;
+
private final IFaceServiceReceiver mReceiver = new IFaceServiceReceiver.Stub() {
@Override
public void onEnrollResult(Face face, int remaining) {
@@ -116,7 +114,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
};
BiometricTestSessionImpl(@NonNull Context context, int sensorId,
- @NonNull ITestSessionCallback callback, @NonNull Face10 face10,
+ @NonNull ITestSessionCallback callback,
+ @NonNull Face10 face10,
@NonNull Face10.HalResultController halResultController) {
mContext = context;
mSensorId = sensorId;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index c0a119ff5f1e..0e0ee1966024 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -62,8 +62,10 @@ import com.android.server.biometrics.sensors.AuthenticationConsumer;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
+import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
import com.android.server.biometrics.sensors.EnumerateConsumer;
import com.android.server.biometrics.sensors.ErrorConsumer;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -110,6 +112,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
private boolean mTestHalEnabled;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
+ @NonNull private final BiometricStateCallback mBiometricStateCallback;
@NonNull private final Context mContext;
@NonNull private final BiometricScheduler mScheduler;
@NonNull private final Handler mHandler;
@@ -336,6 +339,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@VisibleForTesting
Face10(@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
@@ -343,6 +347,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@NonNull BiometricContext biometricContext) {
mSensorProperties = sensorProps;
mContext = context;
+ mBiometricStateCallback = biometricStateCallback;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
mHandler = handler;
@@ -366,11 +371,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
}
public static Face10 newInstance(@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
- return new Face10(context, sensorProps, lockoutResetDispatcher, handler,
- new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
+ return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
+ handler, new BiometricScheduler(TAG, BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */),
BiometricContext.getInstance(context));
}
@@ -615,8 +621,19 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
+ public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+ mBiometricStateCallback.onClientStarted(clientMonitor);
+ }
+
+ @Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
+ mBiometricStateCallback.onClientFinished(clientMonitor, success);
if (success) {
// Update authenticatorIds
scheduleUpdateActiveUserWithoutHandler(client.getTargetUserId());
@@ -661,7 +678,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric, mLockoutTracker,
mUsageStats, allowBackgroundAuthentication, isKeyguardBypassEnabled);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
}
@@ -696,7 +713,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
}
@@ -714,7 +731,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
}
@@ -806,14 +823,15 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext, enrolledList,
FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client, callback);
+ mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
+ mBiometricStateCallback));
});
}
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable ClientMonitorCallback callback) {
- scheduleInternalCleanup(userId, callback);
+ scheduleInternalCleanup(userId, mBiometricStateCallback);
}
@Override
@@ -1011,7 +1029,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
- return new BiometricTestSessionImpl(mContext, mSensorId, callback, this,
- mHalResultController);
+ return new BiometricTestSessionImpl(mContext, mSensorId, callback,
+ this, mHalResultController);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index b0dc28ddce96..156e6bb503ec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -52,6 +52,7 @@ import android.hardware.fingerprint.IFingerprintClientActiveCallback;
import android.hardware.fingerprint.IFingerprintService;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Build;
@@ -874,6 +875,14 @@ public class FingerprintService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ for (ServiceProvider provider : mRegistry.getProviders()) {
+ provider.setUdfpsOverlay(controller);
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
public void onPowerPressed() {
for (ServiceProvider provider : mRegistry.getProviders()) {
provider.onPowerPressed();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 0c29f5615b4c..05c2e2919a11 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -26,6 +26,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
@@ -129,6 +130,12 @@ public interface ServiceProvider extends
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
+ /**
+ * Sets udfps overlay
+ * @param controller udfps overlay
+ */
+ void setUdfpsOverlay(@NonNull IUdfpsOverlay controller);
+
void onPowerPressed();
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 2e5663db57b5..7f1fb1cfc4bd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -33,6 +33,7 @@ import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.Handler;
@@ -118,6 +119,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
@NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull Handler handler,
@@ -145,7 +147,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
mHandler = handler;
@@ -248,8 +251,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
if (authenticated && mSensorProps.isAnySidefpsType()) {
if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
- onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0,
- true);
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+ 0, true);
return;
}
delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 0e89814c6ad2..52822347e96f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -54,12 +55,15 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements
@NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
+ @Nullable IUdfpsOverlayController udfpsOverlayController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
+ boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ null /* sideFpsController*/, udfpsOverlay);
}
@Override
@@ -82,7 +86,8 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+ mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+ this);
try {
mCancellationSignal = doDetectInteraction();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 612d90670888..7e5d39fdef1a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -30,6 +30,7 @@ import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.keymaster.HardwareAuthToken;
import android.os.IBinder;
@@ -86,6 +87,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
// UDFPS haptics occur when an image is acquired (instead of when the result is known)
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -93,7 +95,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
biometricContext);
setRequestId(requestId);
mSensorProps = sensorProps;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mMaxTemplatesPerUser = maxTemplatesPerUser;
mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
@@ -162,7 +165,8 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+ mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+ this);
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 17ba07f2c2bd..a42ff9a8a2ba 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -40,6 +40,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Binder;
import android.os.Handler;
@@ -108,6 +109,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ @Nullable private IUdfpsOverlay mUdfpsOverlay;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -383,29 +385,20 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
mSensors.get(sensorId).getSensorProperties(),
- mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
- scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
-
- @Override
- public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- mBiometricStateCallback.onClientStarted(clientMonitor);
- }
-
- @Override
- public void onBiometricAction(int action) {
- mBiometricStateCallback.onBiometricAction(action);
- }
-
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+ maxTemplatesPerUser, enrollReason);
+ scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
+ mBiometricStateCallback, new ClientMonitorCallback() {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- mBiometricStateCallback.onClientFinished(clientMonitor, success);
+ ClientMonitorCallback.super.onClientFinished(clientMonitor, success);
if (success) {
scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
scheduleInvalidationRequest(sensorId, userId);
}
}
- });
+ }));
});
return id;
}
@@ -428,7 +421,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext,
- mUdfpsOverlayController, isStrongBiometric);
+ mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -449,10 +442,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
- mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+ allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties(), mHandler,
- Utils.getCurrentStrength(sensorId),
- SystemClock.elapsedRealtimeClock());
+ Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -659,6 +652,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
@Override
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ mUdfpsOverlay = controller;
+ }
+
+ @Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
if (mSensors.contains(sensorId)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 0e6df8e0df77..dbc96df9c458 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -38,6 +38,7 @@ import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintServiceReceiver;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -120,6 +121,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ @Nullable private IUdfpsOverlay mUdfpsOverlay;
@NonNull private final BiometricContext mBiometricContext;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@@ -594,7 +596,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
BiometricsProtoEnums.CLIENT_UNKNOWN),
mBiometricContext,
- mUdfpsOverlayController, mSidefpsController,
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -640,7 +642,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mLazyDaemon, token, id, listener, userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
- mBiometricContext, mUdfpsOverlayController,
+ mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -664,7 +666,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
- mUdfpsOverlayController, mSidefpsController,
+ mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
allowBackgroundAuthentication, mSensorProperties);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -853,6 +855,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
@Override
+ public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+ mUdfpsOverlay = controller;
+ }
+
+ @Override
public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
boolean clearSchedulerBuffer) {
final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 0d620fd3a9e4..56fa36ec20e2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -76,15 +77,18 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
@NonNull LockoutFrameworkImpl lockoutTracker,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
boolean allowBackgroundAuthentication,
@NonNull FingerprintSensorPropertiesInternal sensorProps) {
super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
- isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
- false /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
+ isStrongBiometric, taskStackListener, lockoutTracker,
+ allowBackgroundAuthentication, false /* shouldVibrate */,
+ false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index c2929d0f15b2..3e9b8ef7fab4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -23,6 +23,7 @@ import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricOverlayConstants;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -62,11 +63,13 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
int sensorId,
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
- @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
+ @Nullable IUdfpsOverlayController udfpsOverlayController,
+ @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, biometricLogger, biometricContext);
setRequestId(requestId);
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ null /* sideFpsController */, udfpsOverlay);
mIsStrongBiometric = isStrongBiometric;
}
@@ -92,7 +95,8 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+ mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+ this);
try {
getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
@@ -128,8 +132,8 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
}
@Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated,
- ArrayList<Byte> hardwareAuthToken) {
+ public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
+ boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
getLogger().logOnAuthenticated(getContext(), getOperationContext(),
authenticated, false /* requireConfirmation */,
getTargetUserId(), false /* isBiometricPrompt */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 5d9af5322c2e..3371cec32fde 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -26,6 +26,7 @@ import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.IBinder;
import android.os.RemoteException;
@@ -67,12 +68,14 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
@NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
+ @Nullable IUdfpsOverlay udfpsOverlay,
@FingerprintManager.EnrollReason int enrollReason) {
super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
biometricContext);
setRequestId(requestId);
- mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+ mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+ sidefpsController, udfpsOverlay);
mEnrollReason = enrollReason;
if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -102,7 +105,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
@Override
protected void startHalOperation() {
- mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+ mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+ this);
BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
try {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6795b6b4158d..45b0f0a6d04a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -79,6 +79,7 @@ import android.net.NetworkScore;
import android.net.RouteInfo;
import android.net.UidRangeParcel;
import android.net.UnderlyingNetworkInfo;
+import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnProfileState;
import android.net.VpnService;
@@ -226,6 +227,16 @@ public class Vpn {
private static final int VPN_DEFAULT_SCORE = 101;
/**
+ * The reset session timer for data stall. If a session has not successfully revalidated after
+ * the delay, the session will be torn down and restarted in an attempt to recover. Delay
+ * counter is reset on successful validation only.
+ *
+ * <p>If retries have exceeded the length of this array, the last entry in the array will be
+ * used as a repeating interval.
+ */
+ private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L};
+
+ /**
* The initial token value of IKE session.
*/
private static final int STARTING_TOKEN = -1;
@@ -271,6 +282,7 @@ public class Vpn {
private final UserManager mUserManager;
private final VpnProfileStore mVpnProfileStore;
+ protected boolean mDataStallSuspected = false;
@VisibleForTesting
VpnProfileStore getVpnProfileStore() {
@@ -522,12 +534,30 @@ public class Vpn {
@NonNull LinkProperties lp,
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
- @Nullable NetworkProvider provider) {
+ @Nullable NetworkProvider provider,
+ @Nullable ValidationStatusCallback callback) {
return new VpnNetworkAgentWrapper(
- context, looper, logTag, nc, lp, score, config, provider);
+ context, looper, logTag, nc, lp, score, config, provider, callback);
+ }
+
+ /**
+ * Get the length of time to wait before resetting the ike session when a data stall is
+ * suspected.
+ */
+ public long getDataStallResetSessionSeconds(int count) {
+ if (count >= DATA_STALL_RESET_DELAYS_SEC.length) {
+ return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1];
+ } else {
+ return DATA_STALL_RESET_DELAYS_SEC[count];
+ }
}
}
+ @VisibleForTesting
+ interface ValidationStatusCallback {
+ void onValidationStatus(int status);
+ }
+
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
@UserIdInt int userId, VpnProfileStore vpnProfileStore) {
this(looper, context, new Dependencies(), netService, netd, userId, vpnProfileStore,
@@ -1460,6 +1490,11 @@ public class Vpn {
@GuardedBy("this")
private void agentConnect() {
+ agentConnect(null /* validationCallback */);
+ }
+
+ @GuardedBy("this")
+ private void agentConnect(@Nullable ValidationStatusCallback validationCallback) {
LinkProperties lp = makeLinkProperties();
// VPN either provide a default route (IPv4 or IPv6 or both), or they are a split tunnel
@@ -1507,7 +1542,7 @@ public class Vpn {
mNetworkAgent = mDeps.newNetworkAgent(mContext, mLooper, NETWORKTYPE /* logtag */,
mNetworkCapabilities, lp,
new NetworkScore.Builder().setLegacyInt(VPN_DEFAULT_SCORE).build(),
- networkAgentConfig, mNetworkProvider);
+ networkAgentConfig, mNetworkProvider, validationCallback);
final long token = Binder.clearCallingIdentity();
try {
mNetworkAgent.register();
@@ -2723,7 +2758,7 @@ public class Vpn {
@Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture;
@Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture;
-
+ @Nullable private ScheduledFuture<?> mScheduledHandleDataStallFuture;
/** Signal to ensure shutdown is honored even if a new Network is connected. */
private boolean mIsRunning = true;
@@ -2750,6 +2785,14 @@ public class Vpn {
private boolean mMobikeEnabled = false;
/**
+ * The number of attempts to reset the IKE session since the last successful connection.
+ *
+ * <p>This variable controls the retry delay, and is reset when the VPN pass network
+ * validation.
+ */
+ private int mDataStallRetryCount = 0;
+
+ /**
* The number of attempts since the last successful connection.
*
* <p>This variable controls the retry delay, and is reset when a new IKE session is
@@ -2931,7 +2974,7 @@ public class Vpn {
if (isSettingsVpnLocked()) {
prepareStatusIntent();
}
- agentConnect();
+ agentConnect(this::onValidationStatus);
return; // Link properties are already sent.
} else {
// Underlying networks also set in agentConnect()
@@ -3200,18 +3243,52 @@ public class Vpn {
// Ignore stale runner.
if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return;
- // Handle the report only for current VPN network.
+ // Handle the report only for current VPN network. If data stall is already
+ // reported, ignoring the other reports. It means that the stall is not
+ // recovered by MOBIKE and should be on the way to reset the ike session.
if (mNetworkAgent != null
- && mNetworkAgent.getNetwork().equals(report.getNetwork())) {
+ && mNetworkAgent.getNetwork().equals(report.getNetwork())
+ && !mDataStallSuspected) {
Log.d(TAG, "Data stall suspected");
// Trigger MOBIKE.
maybeMigrateIkeSession(mActiveNetwork);
+ mDataStallSuspected = true;
}
}
}
}
+ public void onValidationStatus(int status) {
+ if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
+ // No data stall now. Reset it.
+ mExecutor.execute(() -> {
+ mDataStallSuspected = false;
+ mDataStallRetryCount = 0;
+ if (mScheduledHandleDataStallFuture != null) {
+ Log.d(TAG, "Recovered from stall. Cancel pending reset action.");
+ mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleDataStallFuture = null;
+ }
+ });
+ } else {
+ // Skip other invalid status if the scheduled recovery exists.
+ if (mScheduledHandleDataStallFuture != null) return;
+
+ mScheduledHandleDataStallFuture = mExecutor.schedule(() -> {
+ if (mDataStallSuspected) {
+ Log.d(TAG, "Reset session to recover stalled network");
+ // This will reset old state if it exists.
+ startIkeSession(mActiveNetwork);
+ }
+
+ // Reset mScheduledHandleDataStallFuture since it's already run on executor
+ // thread.
+ mScheduledHandleDataStallFuture = null;
+ }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS);
+ }
+ }
+
/**
* Handles loss of the default underlying network
*
@@ -4339,6 +4416,7 @@ public class Vpn {
// un-finalized.
@VisibleForTesting
public static class VpnNetworkAgentWrapper extends NetworkAgent {
+ private final ValidationStatusCallback mCallback;
/** Create an VpnNetworkAgentWrapper */
public VpnNetworkAgentWrapper(
@NonNull Context context,
@@ -4348,8 +4426,10 @@ public class Vpn {
@NonNull LinkProperties lp,
@NonNull NetworkScore score,
@NonNull NetworkAgentConfig config,
- @Nullable NetworkProvider provider) {
+ @Nullable NetworkProvider provider,
+ @Nullable ValidationStatusCallback callback) {
super(context, looper, logTag, nc, lp, score, config, provider);
+ mCallback = callback;
}
/** Update the LinkProperties */
@@ -4371,6 +4451,13 @@ public class Vpn {
public void onNetworkUnwanted() {
// We are user controlled, not driven by NetworkRequest.
}
+
+ @Override
+ public void onValidationStatus(int status, Uri redirectUri) {
+ if (mCallback != null) {
+ mCallback.onValidationStatus(status);
+ }
+ }
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 7e80b7d5b0ac..e907ebfa6471 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -127,6 +127,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
@@ -151,6 +152,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
+
/**
* Manages attached displays.
* <p>
@@ -1900,6 +1902,14 @@ public final class DisplayManagerService extends SystemService {
if (displayDevice == null) {
return;
}
+ if (mLogicalDisplayMapper.getDisplayLocked(displayDevice)
+ .getDisplayInfoLocked().type == Display.TYPE_INTERNAL) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BRIGHTNESS_CONFIGURATION_UPDATED,
+ c.getCurve().first,
+ c.getCurve().second,
+ // should not be logged for virtual displays
+ uniqueId);
+ }
mPersistentDataStore.setBrightnessConfigurationForDisplayLocked(c, displayDevice,
userSerial, packageName);
} finally {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 6e2ccebb6ff4..4ca4817cc4fe 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -23,12 +23,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -38,6 +40,8 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.hardware.display.AmbientDisplayConfiguration;
+import android.net.Uri;
+import android.os.BatteryManager;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -72,6 +76,8 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -88,6 +94,15 @@ public final class DreamManagerService extends SystemService {
private static final String DOZE_WAKE_LOCK_TAG = "dream:doze";
private static final String DREAM_WAKE_LOCK_TAG = "dream:dream";
+ /** Constants for the when to activate dreams. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DREAM_ON_DOCK, DREAM_ON_CHARGE, DREAM_ON_DOCK_OR_CHARGE})
+ public @interface WhenToDream {}
+ private static final int DREAM_DISABLED = 0x0;
+ private static final int DREAM_ON_DOCK = 0x1;
+ private static final int DREAM_ON_CHARGE = 0x2;
+ private static final int DREAM_ON_DOCK_OR_CHARGE = 0x3;
+
private final Object mLock = new Object();
private final Context mContext;
@@ -101,12 +116,20 @@ public final class DreamManagerService extends SystemService {
private final DreamUiEventLogger mDreamUiEventLogger;
private final ComponentName mAmbientDisplayComponent;
private final boolean mDismissDreamOnActivityStart;
+ private final boolean mDreamsOnlyEnabledForSystemUser;
+ private final boolean mDreamsEnabledByDefaultConfig;
+ private final boolean mDreamsActivatedOnChargeByDefault;
+ private final boolean mDreamsActivatedOnDockByDefault;
@GuardedBy("mLock")
private DreamRecord mCurrentDream;
private boolean mForceAmbientDisplayEnabled;
- private final boolean mDreamsOnlyEnabledForSystemUser;
+ private SettingsObserver mSettingsObserver;
+ private boolean mDreamsEnabledSetting;
+ @WhenToDream private int mWhenToDream;
+ private boolean mIsDocked;
+ private boolean mIsCharging;
// A temporary dream component that, when present, takes precedence over user configured dream
// component.
@@ -144,6 +167,37 @@ public final class DreamManagerService extends SystemService {
}
};
+ private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+ }
+ };
+
+ private final BroadcastReceiver mDockStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+ int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+ Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ mIsDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+ }
+ };
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mLock) {
+ updateWhenToDreamSettings();
+ }
+ }
+ }
+
public DreamManagerService(Context context) {
super(context);
mContext = context;
@@ -164,6 +218,14 @@ public final class DreamManagerService extends SystemService {
mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser);
mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
R.bool.config_dismissDreamOnActivityStart);
+
+ mDreamsEnabledByDefaultConfig = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsEnabledByDefault);
+ mDreamsActivatedOnChargeByDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
+ mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+ mSettingsObserver = new SettingsObserver(mHandler);
}
@Override
@@ -197,6 +259,30 @@ public final class DreamManagerService extends SystemService {
DREAM_MANAGER_ORDERED_ID,
mActivityInterceptorCallback);
}
+
+ mContext.registerReceiver(
+ mDockStateReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ IntentFilter chargingIntentFilter = new IntentFilter();
+ chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
+ chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+ mContext.registerReceiver(mChargingReceiver, chargingIntentFilter);
+
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.SCREENSAVER_ENABLED),
+ false, mSettingsObserver, UserHandle.USER_ALL);
+
+ // We don't get an initial broadcast for the batter state, so we have to initialize
+ // directly from BatteryManager.
+ mIsCharging = mContext.getSystemService(BatteryManager.class).isCharging();
+
+ updateWhenToDreamSettings();
}
}
@@ -207,6 +293,14 @@ public final class DreamManagerService extends SystemService {
pw.println("mCurrentDream=" + mCurrentDream);
pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting);
+ pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
+ pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+ pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault);
+ pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault);
+ pw.println("mIsDocked=" + mIsDocked);
+ pw.println("mIsCharging=" + mIsCharging);
+ pw.println("mWhenToDream=" + mWhenToDream);
pw.println("getDozeComponent()=" + getDozeComponent());
pw.println();
@@ -214,7 +308,28 @@ public final class DreamManagerService extends SystemService {
}
}
- /** Whether a real dream is occurring. */
+ private void updateWhenToDreamSettings() {
+ synchronized (mLock) {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ final int activateWhenCharging = (Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+ mDreamsActivatedOnChargeByDefault ? 1 : 0,
+ UserHandle.USER_CURRENT) != 0) ? DREAM_ON_CHARGE : DREAM_DISABLED;
+ final int activateWhenDocked = (Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+ mDreamsActivatedOnDockByDefault ? 1 : 0,
+ UserHandle.USER_CURRENT) != 0) ? DREAM_ON_DOCK : DREAM_DISABLED;
+ mWhenToDream = activateWhenCharging + activateWhenDocked;
+
+ mDreamsEnabledSetting = (Settings.Secure.getIntForUser(resolver,
+ Settings.Secure.SCREENSAVER_ENABLED,
+ mDreamsEnabledByDefaultConfig ? 1 : 0,
+ UserHandle.USER_CURRENT) != 0);
+ }
+ }
+
+ /** Whether a real dream is occurring. */
private boolean isDreamingInternal() {
synchronized (mLock) {
return mCurrentDream != null && !mCurrentDream.isPreview
@@ -236,6 +351,30 @@ public final class DreamManagerService extends SystemService {
}
}
+ /** Whether dreaming can start given user settings and the current dock/charge state. */
+ private boolean canStartDreamingInternal(boolean isScreenOn) {
+ synchronized (mLock) {
+ // Can't start dreaming if we are already dreaming.
+ if (isScreenOn && isDreamingInternal()) {
+ return false;
+ }
+
+ if (!mDreamsEnabledSetting) {
+ return false;
+ }
+
+ if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
+ return mIsCharging;
+ }
+
+ if ((mWhenToDream & DREAM_ON_DOCK) == DREAM_ON_DOCK) {
+ return mIsDocked;
+ }
+
+ return false;
+ }
+ }
+
protected void requestStartDreamFromShell() {
requestDreamInternal();
}
@@ -869,6 +1008,11 @@ public final class DreamManagerService extends SystemService {
}
@Override
+ public boolean canStartDreaming(boolean isScreenOn) {
+ return canStartDreamingInternal(isScreenOn);
+ }
+
+ @Override
public ComponentName getActiveDreamComponent(boolean doze) {
return getActiveDreamComponentInternal(doze);
}
diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
index 5253d34a38f0..d4e8f27a7b34 100644
--- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
@@ -19,28 +19,9 @@ import android.annotation.ArrayRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
/**
* Gets the service name using a framework resources, temporarily changing the service if necessary
@@ -48,259 +29,42 @@ import java.util.List;
*
* @hide
*/
-public final class FrameworkResourcesServiceNameResolver implements ServiceNameResolver {
-
- private static final String TAG = FrameworkResourcesServiceNameResolver.class.getSimpleName();
-
- /** Handler message to {@link #resetTemporaryService(int)} */
- private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+public final class FrameworkResourcesServiceNameResolver extends ServiceNameBaseResolver {
- @NonNull
- private final Context mContext;
- @NonNull
- private final Object mLock = new Object();
- @StringRes
private final int mStringResourceId;
@ArrayRes
private final int mArrayResourceId;
- private final boolean mIsMultiple;
- /**
- * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
- * keyed by {@code userId}.
- *
- * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
- * mIsMultiple is true.
- */
- @GuardedBy("mLock")
- private final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
- /**
- * Map of default services that have been disabled by
- * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
- *
- * <p>Typically used by Shell command and/or CTS tests.
- */
- @GuardedBy("mLock")
- private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
- @Nullable
- private NameResolverListener mOnSetCallback;
- /**
- * When the temporary service will expire (and reset back to the default).
- */
- @GuardedBy("mLock")
- private long mTemporaryServiceExpiration;
-
- /**
- * Handler used to reset the temporary service name.
- */
- @GuardedBy("mLock")
- private Handler mTemporaryHandler;
public FrameworkResourcesServiceNameResolver(@NonNull Context context,
@StringRes int resourceId) {
- mContext = context;
+ super(context, false);
mStringResourceId = resourceId;
mArrayResourceId = -1;
- mIsMultiple = false;
}
public FrameworkResourcesServiceNameResolver(@NonNull Context context,
@ArrayRes int resourceId, boolean isMultiple) {
+ super(context, isMultiple);
if (!isMultiple) {
throw new UnsupportedOperationException("Please use "
+ "FrameworkResourcesServiceNameResolver(context, @StringRes int) constructor "
+ "if single service mode is requested.");
}
- mContext = context;
mStringResourceId = -1;
mArrayResourceId = resourceId;
- mIsMultiple = true;
- }
-
- @Override
- public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
- synchronized (mLock) {
- this.mOnSetCallback = callback;
- }
- }
-
- @Override
- public String getServiceName(@UserIdInt int userId) {
- String[] serviceNames = getServiceNameList(userId);
- return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
- }
-
- @Override
- public String getDefaultServiceName(@UserIdInt int userId) {
- String[] serviceNames = getDefaultServiceNameList(userId);
- return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
- }
-
- /**
- * Gets the default list of the service names for the given user.
- *
- * <p>Typically implemented by services which want to provide multiple backends.
- */
- @Override
- public String[] getServiceNameList(int userId) {
- synchronized (mLock) {
- String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
- if (temporaryNames != null) {
- // Always log it, as it should only be used on CTS or during development
- Slog.w(TAG, "getServiceName(): using temporary name "
- + Arrays.toString(temporaryNames) + " for user " + userId);
- return temporaryNames;
- }
- final boolean disabled = mDefaultServicesDisabled.get(userId);
- if (disabled) {
- // Always log it, as it should only be used on CTS or during development
- Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
- + "user " + userId);
- return null;
- }
- return getDefaultServiceNameList(userId);
-
- }
- }
-
- /**
- * Gets the default list of the service names for the given user.
- *
- * <p>Typically implemented by services which want to provide multiple backends.
- */
- @Override
- public String[] getDefaultServiceNameList(int userId) {
- synchronized (mLock) {
- if (mIsMultiple) {
- String[] serviceNameList = mContext.getResources().getStringArray(mArrayResourceId);
- // Filter out unimplemented services
- // Initialize the validated array as null because we do not know the final size.
- List<String> validatedServiceNameList = new ArrayList<>();
- try {
- for (int i = 0; i < serviceNameList.length; i++) {
- if (TextUtils.isEmpty(serviceNameList[i])) {
- continue;
- }
- ComponentName serviceComponent = ComponentName.unflattenFromString(
- serviceNameList[i]);
- ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
- serviceComponent,
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
- if (serviceInfo != null) {
- validatedServiceNameList.add(serviceNameList[i]);
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Could not validate provided services.", e);
- }
- String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
- return validatedServiceNameList.toArray(validatedServiceNameArray);
- } else {
- final String name = mContext.getString(mStringResourceId);
- return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
- }
- }
- }
-
- @Override
- public boolean isConfiguredInMultipleMode() {
- return mIsMultiple;
- }
-
- @Override
- public boolean isTemporary(@UserIdInt int userId) {
- synchronized (mLock) {
- return mTemporaryServiceNamesList.get(userId) != null;
- }
}
@Override
- public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
- int durationMs) {
- setTemporaryServices(userId, new String[]{componentName}, durationMs);
- }
-
- @Override
- public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
- synchronized (mLock) {
- mTemporaryServiceNamesList.put(userId, componentNames);
-
- if (mTemporaryHandler == null) {
- mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
- @Override
- public void handleMessage(Message msg) {
- if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
- synchronized (mLock) {
- resetTemporaryService(userId);
- }
- } else {
- Slog.wtf(TAG, "invalid handler msg: " + msg);
- }
- }
- };
- } else {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- }
- mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
- mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
- for (int i = 0; i < componentNames.length; i++) {
- notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
- /* isTemporary= */ true);
- }
- }
- }
-
- @Override
- public void resetTemporaryService(@UserIdInt int userId) {
- synchronized (mLock) {
- Slog.i(TAG, "resetting temporary service for user " + userId + " from "
- + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
- mTemporaryServiceNamesList.remove(userId);
- if (mTemporaryHandler != null) {
- mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
- mTemporaryHandler = null;
- }
- notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
- /* isTemporary= */ false);
- }
- }
-
- @Override
- public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
- synchronized (mLock) {
- final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
- if (currentlyEnabled == enabled) {
- Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
- return false;
- }
- if (enabled) {
- Slog.i(TAG, "disabling default service for user " + userId);
- mDefaultServicesDisabled.removeAt(userId);
- } else {
- Slog.i(TAG, "enabling default service for user " + userId);
- mDefaultServicesDisabled.put(userId, true);
- }
- }
- return true;
+ public String[] readServiceNameList(int userId) {
+ return mContext.getResources().getStringArray(mArrayResourceId);
}
+ @Nullable
@Override
- public boolean isDefaultServiceEnabled(int userId) {
- synchronized (mLock) {
- return isDefaultServiceEnabledLocked(userId);
- }
+ public String readServiceName(int userId) {
+ return mContext.getResources().getString(mStringResourceId);
}
- private boolean isDefaultServiceEnabledLocked(int userId) {
- return !mDefaultServicesDisabled.get(userId);
- }
-
- @Override
- public String toString() {
- synchronized (mLock) {
- return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
- }
- }
// TODO(b/117779333): support proto
@Override
@@ -314,31 +78,4 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR
pw.print(mDefaultServicesDisabled.size());
}
}
-
- // TODO(b/117779333): support proto
- @Override
- public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
- synchronized (mLock) {
- final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
- if (temporaryNames != null) {
- pw.print("tmpName=");
- pw.print(Arrays.toString(temporaryNames));
- final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
- pw.print(" (expires in ");
- TimeUtils.formatDuration(ttl, pw);
- pw.print("), ");
- }
- pw.print("defaultName=");
- pw.print(getDefaultServiceName(userId));
- final boolean disabled = mDefaultServicesDisabled.get(userId);
- pw.println(disabled ? " (disabled)" : " (enabled)");
- }
- }
-
- private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
- @Nullable String newTemporaryName, boolean isTemporary) {
- if (mOnSetCallback != null) {
- mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
- }
- }
}
diff --git a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
index cac7f53aad66..17d75e600c36 100644
--- a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
@@ -19,8 +19,11 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
import java.io.PrintWriter;
+import java.util.Set;
/**
* Gets the service name using a property from the {@link android.provider.Settings.Secure}
@@ -28,21 +31,34 @@ import java.io.PrintWriter;
*
* @hide
*/
-public final class SecureSettingsServiceNameResolver implements ServiceNameResolver {
+public final class SecureSettingsServiceNameResolver extends ServiceNameBaseResolver {
+ /**
+ * The delimiter to be used to parse the secure settings string. Services must make sure
+ * that this delimiter is used while adding component names to their secure setting property.
+ */
+ private static final char COMPONENT_NAME_SEPARATOR = ':';
- private final @NonNull Context mContext;
+ private final TextUtils.SimpleStringSplitter mStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
@NonNull
private final String mProperty;
public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property) {
- mContext = context;
- mProperty = property;
+ this(context, property, /*isMultiple*/false);
}
- @Override
- public String getDefaultServiceName(@UserIdInt int userId) {
- return Settings.Secure.getStringForUser(mContext.getContentResolver(), mProperty, userId);
+ /**
+ *
+ * @param context the context required to retrieve the secure setting value
+ * @param property name of the secure setting key
+ * @param isMultiple true if the system service using this resolver needs to connect to
+ * multiple remote services, false otherwise
+ */
+ public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property,
+ boolean isMultiple) {
+ super(context, isMultiple);
+ mProperty = property;
}
// TODO(b/117779333): support proto
@@ -61,4 +77,34 @@ public final class SecureSettingsServiceNameResolver implements ServiceNameResol
public String toString() {
return "SecureSettingsServiceNameResolver[" + mProperty + "]";
}
+
+ @Override
+ public String[] readServiceNameList(int userId) {
+ return parseColonDelimitedServiceNames(
+ Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), mProperty, userId));
+ }
+
+ @Override
+ public String readServiceName(int userId) {
+ return Settings.Secure.getStringForUser(
+ mContext.getContentResolver(), mProperty, userId);
+ }
+
+ private String[] parseColonDelimitedServiceNames(String serviceNames) {
+ final Set<String> delimitedServices = new ArraySet<>();
+ if (!TextUtils.isEmpty(serviceNames)) {
+ final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(serviceNames);
+ while (splitter.hasNext()) {
+ final String str = splitter.next();
+ if (TextUtils.isEmpty(str)) {
+ continue;
+ }
+ delimitedServices.add(str);
+ }
+ }
+ String[] delimitedServicesArray = new String[delimitedServices.size()];
+ return delimitedServices.toArray(delimitedServicesArray);
+ }
}
diff --git a/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
new file mode 100644
index 000000000000..76ea05e36141
--- /dev/null
+++ b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.infra;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Gets the service name using a framework resources, temporarily changing the service if necessary
+ * (typically during CTS tests or service development).
+ *
+ * @hide
+ */
+public abstract class ServiceNameBaseResolver implements ServiceNameResolver {
+
+ private static final String TAG = ServiceNameBaseResolver.class.getSimpleName();
+
+ /** Handler message to {@link #resetTemporaryService(int)} */
+ private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
+ @NonNull
+ protected final Context mContext;
+ @NonNull
+ protected final Object mLock = new Object();
+
+ protected final boolean mIsMultiple;
+ /**
+ * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
+ * keyed by {@code userId}.
+ *
+ * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
+ * mIsMultiple is true.
+ */
+ @GuardedBy("mLock")
+ protected final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
+ /**
+ * Map of default services that have been disabled by
+ * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
+ *
+ * <p>Typically used by Shell command and/or CTS tests.
+ */
+ @GuardedBy("mLock")
+ protected final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
+ @Nullable
+ private NameResolverListener mOnSetCallback;
+ /**
+ * When the temporary service will expire (and reset back to the default).
+ */
+ @GuardedBy("mLock")
+ private long mTemporaryServiceExpiration;
+
+ /**
+ * Handler used to reset the temporary service name.
+ */
+ @GuardedBy("mLock")
+ private Handler mTemporaryHandler;
+
+ protected ServiceNameBaseResolver(Context context, boolean isMultiple) {
+ mContext = context;
+ mIsMultiple = isMultiple;
+ }
+
+ @Override
+ public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
+ synchronized (mLock) {
+ this.mOnSetCallback = callback;
+ }
+ }
+
+ @Override
+ public String getServiceName(@UserIdInt int userId) {
+ String[] serviceNames = getServiceNameList(userId);
+ return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+ }
+
+ @Override
+ public String getDefaultServiceName(@UserIdInt int userId) {
+ String[] serviceNames = getDefaultServiceNameList(userId);
+ return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+ }
+
+ /**
+ * Gets the default list of the service names for the given user.
+ *
+ * <p>Typically implemented by services which want to provide multiple backends.
+ */
+ @Override
+ public String[] getServiceNameList(int userId) {
+ synchronized (mLock) {
+ String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+ if (temporaryNames != null) {
+ // Always log it, as it should only be used on CTS or during development
+ Slog.w(TAG, "getServiceName(): using temporary name "
+ + Arrays.toString(temporaryNames) + " for user " + userId);
+ return temporaryNames;
+ }
+ final boolean disabled = mDefaultServicesDisabled.get(userId);
+ if (disabled) {
+ // Always log it, as it should only be used on CTS or during development
+ Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
+ + "user " + userId);
+ return null;
+ }
+ return getDefaultServiceNameList(userId);
+
+ }
+ }
+
+ /**
+ * Base classes must override this to read from the desired config e.g. framework resource,
+ * secure settings etc.
+ */
+ @Nullable
+ public abstract String[] readServiceNameList(int userId);
+
+ /**
+ * Base classes must override this to read from the desired config e.g. framework resource,
+ * secure settings etc.
+ */
+ @Nullable
+ public abstract String readServiceName(int userId);
+
+ /**
+ * Gets the default list of the service names for the given user.
+ *
+ * <p>Typically implemented by services which want to provide multiple backends.
+ */
+ @Override
+ public String[] getDefaultServiceNameList(int userId) {
+ synchronized (mLock) {
+ if (mIsMultiple) {
+ String[] serviceNameList = readServiceNameList(userId);
+ // Filter out unimplemented services
+ // Initialize the validated array as null because we do not know the final size.
+ List<String> validatedServiceNameList = new ArrayList<>();
+ try {
+ for (int i = 0; i < serviceNameList.length; i++) {
+ if (TextUtils.isEmpty(serviceNameList[i])) {
+ continue;
+ }
+ ComponentName serviceComponent = ComponentName.unflattenFromString(
+ serviceNameList[i]);
+ ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+ serviceComponent,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ if (serviceInfo != null) {
+ validatedServiceNameList.add(serviceNameList[i]);
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not validate provided services.", e);
+ }
+ String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
+ return validatedServiceNameList.toArray(validatedServiceNameArray);
+ } else {
+ final String name = readServiceName(userId);
+ return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
+ }
+ }
+ }
+
+ @Override
+ public boolean isConfiguredInMultipleMode() {
+ return mIsMultiple;
+ }
+
+ @Override
+ public boolean isTemporary(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return mTemporaryServiceNamesList.get(userId) != null;
+ }
+ }
+
+ @Override
+ public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
+ int durationMs) {
+ setTemporaryServices(userId, new String[]{componentName}, durationMs);
+ }
+
+ @Override
+ public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
+ synchronized (mLock) {
+ mTemporaryServiceNamesList.put(userId, componentNames);
+
+ if (mTemporaryHandler == null) {
+ mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+ synchronized (mLock) {
+ resetTemporaryService(userId);
+ }
+ } else {
+ Slog.wtf(TAG, "invalid handler msg: " + msg);
+ }
+ }
+ };
+ } else {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ }
+ mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
+ mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+ for (int i = 0; i < componentNames.length; i++) {
+ notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
+ /* isTemporary= */ true);
+ }
+ }
+ }
+
+ @Override
+ public void resetTemporaryService(@UserIdInt int userId) {
+ synchronized (mLock) {
+ Slog.i(TAG, "resetting temporary service for user " + userId + " from "
+ + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
+ mTemporaryServiceNamesList.remove(userId);
+ if (mTemporaryHandler != null) {
+ mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+ mTemporaryHandler = null;
+ }
+ notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
+ /* isTemporary= */ false);
+ }
+ }
+
+ @Override
+ public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
+ synchronized (mLock) {
+ final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
+ if (currentlyEnabled == enabled) {
+ Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
+ return false;
+ }
+ if (enabled) {
+ Slog.i(TAG, "disabling default service for user " + userId);
+ mDefaultServicesDisabled.removeAt(userId);
+ } else {
+ Slog.i(TAG, "enabling default service for user " + userId);
+ mDefaultServicesDisabled.put(userId, true);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isDefaultServiceEnabled(int userId) {
+ synchronized (mLock) {
+ return isDefaultServiceEnabledLocked(userId);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean isDefaultServiceEnabledLocked(int userId) {
+ return !mDefaultServicesDisabled.get(userId);
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mLock) {
+ return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
+ }
+ }
+
+ // TODO(b/117779333): support proto
+ @Override
+ public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
+ synchronized (mLock) {
+ final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+ if (temporaryNames != null) {
+ pw.print("tmpName=");
+ pw.print(Arrays.toString(temporaryNames));
+ final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
+ pw.print(" (expires in ");
+ TimeUtils.formatDuration(ttl, pw);
+ pw.print("), ");
+ }
+ pw.print("defaultName=");
+ pw.print(getDefaultServiceName(userId));
+ final boolean disabled = mDefaultServicesDisabled.get(userId);
+ pw.println(disabled ? " (disabled)" : " (enabled)");
+ }
+ }
+
+ private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
+ @Nullable String newTemporaryName, boolean isTemporary) {
+ if (mOnSetCallback != null) {
+ mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 36199debaa6e..9d4f18113555 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -371,6 +371,17 @@ final class BatteryController {
}
}
+ public void notifyStylusGestureStarted(int deviceId, long eventTime) {
+ synchronized (mLock) {
+ final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+ if (monitor == null) {
+ return;
+ }
+
+ monitor.onStylusGestureStarted(eventTime);
+ }
+ }
+
public void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
final String indent = prefix + " ";
@@ -557,6 +568,8 @@ final class BatteryController {
public void onTimeout(long eventTime) {}
+ public void onStylusGestureStarted(long eventTime) {}
+
// Returns the current battery state that can be used to notify listeners BatteryController.
public State getBatteryStateForReporting() {
return new State(mState);
@@ -600,6 +613,22 @@ final class BatteryController {
}
@Override
+ public void onStylusGestureStarted(long eventTime) {
+ processChangesAndNotify(eventTime, (time) -> {
+ final boolean wasValid = mValidityTimeoutCallback != null;
+ if (!wasValid && mState.capacity == 0.f) {
+ // Handle a special case where the USI device reports a battery capacity of 0
+ // at boot until the first battery update. To avoid wrongly sending out a
+ // battery capacity of 0 if we detect stylus presence before the capacity
+ // is first updated, do not validate the battery state when the state is not
+ // valid and the capacity is 0.
+ return;
+ }
+ markUsiBatteryValid();
+ });
+ }
+
+ @Override
public void onTimeout(long eventTime) {
processChangesAndNotify(eventTime, (time) -> markUsiBatteryInvalid());
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 69b0e65e38da..31f63d864f6c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -19,6 +19,8 @@ package com.android.server.input;
import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
import static android.view.KeyEvent.KEYCODE_UNKNOWN;
+import android.Manifest;
+import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerInternal;
@@ -2671,6 +2673,12 @@ public class InputManagerService extends IInputManager.Stub
mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
}
+ @EnforcePermission(Manifest.permission.BLUETOOTH)
+ @Override
+ public String getInputDeviceBluetoothAddress(int deviceId) {
+ return mNative.getBluetoothAddress(deviceId);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -3052,6 +3060,12 @@ public class InputManagerService extends IInputManager.Stub
com.android.internal.R.bool.config_perDisplayFocusEnabled);
}
+ // Native callback.
+ @SuppressWarnings("unused")
+ private void notifyStylusGestureStarted(int deviceId, long eventTime) {
+ mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+ }
+
/**
* Flatten a map into a string list, with value positioned directly next to the
* key.
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 63c0a88bf467..cfa7fb141be1 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -204,6 +204,9 @@ interface NativeInputManagerService {
/** Set the displayId on which the mouse cursor should be shown. */
void setPointerDisplayId(int displayId);
+ /** Get the bluetooth address of an input device if known, otherwise return null. */
+ String getBluetoothAddress(int deviceId);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -418,5 +421,8 @@ interface NativeInputManagerService {
@Override
public native void setPointerDisplayId(int displayId);
+
+ @Override
+ public native String getBluetoothAddress(int deviceId);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 76495b17c984..9cb8f43c452c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4601,12 +4601,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
if (!setVisible) {
if (mCurClient != null) {
- // IMMS only knows of focused window, not the actual IME target.
- // e.g. it isn't aware of any window that has both
- // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
- // Send it to window manager to hide IME from IME target window.
- // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
- // actual IME target.
mWindowManagerInternal.hideIme(
mHideRequestWindowMap.get(windowToken),
mCurClient.mSelfReportedDisplayId);
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 364f6db28f03..4ce03205fc1c 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -364,16 +364,19 @@ public class LocaleManagerService extends SystemService {
// 1.) A normal, non-privileged app querying its own locale.
// 2.) The installer of the given app querying locales of a package installed by said
// installer.
- // 3.) The current input method querying locales of another package.
+ // 3.) The current input method querying locales of the current foreground app.
// 4.) A privileged system service querying locales of another package.
// The least privileged case is a normal app performing a query, so check that first and get
// locales if the package name is owned by the app. Next check if the calling app is the
// installer of the given app and get locales. Finally check if the calling app is the
- // current input method. If neither conditions matched, check if the caller has the
- // necessary permission and fetch locales.
+ // current input method, and that app is querying locales of the current foreground app. If
+ // neither conditions matched, check if the caller has the necessary permission and fetch
+ // locales.
if (!isPackageOwnedByCaller(appPackageName, userId)
&& !isCallerInstaller(appPackageName, userId)
- && !isCallerFromCurrentInputMethod(userId)) {
+ && !(isCallerFromCurrentInputMethod(userId)
+ && mActivityManagerInternal.isAppForeground(
+ getPackageUid(appPackageName, userId)))) {
enforceReadAppSpecificLocalesPermission();
}
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index f653b9381721..439e9bd2624e 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -132,7 +132,7 @@ class MediaRouter2ServiceImpl {
}
}
- mEventLogger.log(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
+ mEventLogger.enqueue(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
}
};
@@ -634,7 +634,7 @@ class MediaRouter2ServiceImpl {
/* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
synchronized (mLock) {
if (mCurrentActiveUserId != newActiveUserId) {
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("switchUser",
"userId: %d", newActiveUserId));
@@ -705,7 +705,7 @@ class MediaRouter2ServiceImpl {
obtainMessage(UserHandler::notifyRouterRegistered,
userRecord.mHandler, routerRecord));
- mEventLogger.log(EventLogger.StringEvent.from("registerRouter2",
+ mEventLogger.enqueue(EventLogger.StringEvent.from("registerRouter2",
"package: %s, uid: %d, pid: %d, router id: %d",
packageName, uid, pid, routerRecord.mRouterId));
}
@@ -718,7 +718,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from(
"unregisterRouter2",
"package: %s, router id: %d",
@@ -744,7 +744,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"setDiscoveryRequestWithRouter2",
"router id: %d, discovery request: %s",
routerRecord.mRouterId, discoveryRequest.toString()));
@@ -766,7 +766,7 @@ class MediaRouter2ServiceImpl {
RouterRecord routerRecord = mAllRouterRecords.get(binder);
if (routerRecord != null) {
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"setRouteVolumeWithRouter2",
"router id: %d, volume: %d",
routerRecord.mRouterId, volume));
@@ -851,7 +851,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"selectRouteWithRouter2",
"router id: %d, route: %s",
routerRecord.mRouterId, route.getId()));
@@ -871,7 +871,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"deselectRouteWithRouter2",
"router id: %d, route: %s",
routerRecord.mRouterId, route.getId()));
@@ -891,7 +891,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"transferToRouteWithRouter2",
"router id: %d, route: %s",
routerRecord.mRouterId, route.getId()));
@@ -921,7 +921,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"setSessionVolumeWithRouter2",
"router id: %d, session: %s, volume: %d",
routerRecord.mRouterId, uniqueSessionId, volume));
@@ -941,7 +941,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(EventLogger.StringEvent.from(
+ mEventLogger.enqueue(EventLogger.StringEvent.from(
"releaseSessionWithRouter2",
"router id: %d, session: %s",
routerRecord.mRouterId, uniqueSessionId));
@@ -983,7 +983,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("registerManager",
"uid: %d, pid: %d, package: %s, userId: %d",
uid, pid, packageName, userId));
@@ -1025,7 +1025,7 @@ class MediaRouter2ServiceImpl {
}
UserRecord userRecord = managerRecord.mUserRecord;
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from(
"unregisterManager",
"package: %s, userId: %d, managerId: %d",
@@ -1045,7 +1045,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("startScan",
"manager: %d", managerRecord.mManagerId));
@@ -1059,7 +1059,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("stopScan",
"manager: %d", managerRecord.mManagerId));
@@ -1076,7 +1076,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("setRouteVolumeWithManager",
"managerId: %d, routeId: %s, volume: %d",
managerRecord.mManagerId, route.getId(), volume));
@@ -1096,7 +1096,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("requestCreateSessionWithManager",
"managerId: %d, routeId: %s",
managerRecord.mManagerId, route.getId()));
@@ -1146,7 +1146,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("selectRouteWithManager",
"managerId: %d, session: %s, routeId: %s",
managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1172,7 +1172,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("deselectRouteWithManager",
"managerId: %d, session: %s, routeId: %s",
managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1198,7 +1198,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("transferToRouteWithManager",
"managerId: %d, session: %s, routeId: %s",
managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1224,7 +1224,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("setSessionVolumeWithManager",
"managerId: %d, session: %s, volume: %d",
managerRecord.mManagerId, uniqueSessionId, volume));
@@ -1245,7 +1245,7 @@ class MediaRouter2ServiceImpl {
return;
}
- mEventLogger.log(
+ mEventLogger.enqueue(
EventLogger.StringEvent.from("releaseSessionWithManager",
"managerId: %d, session: %s",
managerRecord.mManagerId, uniqueSessionId));
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
new file mode 100644
index 000000000000..df95f86d1e07
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -0,0 +1,130 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * @hide
+ */
+public class BackgroundInstallControlService extends SystemService {
+ private static final String TAG = "BackgroundInstallControlService";
+
+ private final Context mContext;
+ private final BinderService mBinderService;
+ private final IPackageManager mIPackageManager;
+
+ // User ID -> package name -> time diff
+ // The time diff between the last foreground activity installer and
+ // the "onPackageAdded" function call.
+ private final SparseArrayMap<String, Long> mBackgroundInstalledPackages =
+ new SparseArrayMap<>();
+
+ public BackgroundInstallControlService(@NonNull Context context) {
+ this(new InjectorImpl(context));
+ }
+
+ @VisibleForTesting
+ BackgroundInstallControlService(@NonNull Injector injector) {
+ super(injector.getContext());
+ mContext = injector.getContext();
+ mIPackageManager = injector.getIPackageManager();
+ mBinderService = new BinderService(this);
+ }
+
+ private static final class BinderService extends IBackgroundInstallControlService.Stub {
+ final BackgroundInstallControlService mService;
+
+ BinderService(BackgroundInstallControlService service) {
+ mService = service;
+ }
+
+ @Override
+ public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
+ @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+ ParceledListSlice<PackageInfo> packages;
+ try {
+ packages = mService.mIPackageManager.getInstalledPackages(flags, userId);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Package manager not available", e);
+ }
+
+ // TODO(b/244216300): to enable the test the usage by BinaryTransparencyService,
+ // we currently comment out the actual implementation.
+ // The fake implementation is just to filter out the first app of the list.
+ // for (int i = 0, size = packages.getList().size(); i < size; i++) {
+ // String packageName = packages.getList().get(i).packageName;
+ // if (!mBackgroundInstalledPackages.contains(userId, packageName) {
+ // packages.getList().remove(i);
+ // }
+ // }
+ if (packages.getList().size() > 0) {
+ packages.getList().remove(0);
+ }
+ return packages;
+ }
+ }
+
+ /**
+ * Called when the system service should publish a binder service using
+ * {@link #publishBinderService(String, IBinder).}
+ */
+ @Override
+ public void onStart() {
+ publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
+ }
+
+ /**
+ * Dependency injector for {@link #BackgroundInstallControlService)}.
+ */
+ interface Injector {
+ Context getContext();
+
+ IPackageManager getIPackageManager();
+ }
+
+ private static final class InjectorImpl implements Injector {
+ private final Context mContext;
+
+ InjectorImpl(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public IPackageManager getIPackageManager() {
+ return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index f6472a739979..6f5909615db2 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -352,8 +352,7 @@ final class InitAppsHelper {
}
@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})
- private void scanDirTracedLI(File scanDir,
- int parseFlags, int scanFlags,
+ private void scanDirTracedLI(File scanDir, int parseFlags, int scanFlags,
PackageParser2 packageParser, ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
try {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 30ecc1cb4e8f..9d007c92bcc5 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -245,50 +245,42 @@ final class InstallPackageHelper {
@GuardedBy("mPm.mLock")
public AndroidPackage commitReconciledScanResultLocked(
@NonNull ReconciledPackage reconciledPkg, int[] allUsers) {
- final ScanResult result = reconciledPkg.mScanResult;
- final ScanRequest request = result.mRequest;
+ final InstallRequest request = reconciledPkg.mInstallRequest;
// TODO(b/135203078): Move this even further away
- ParsedPackage parsedPackage = request.mParsedPackage;
- if ("android".equals(parsedPackage.getPackageName())) {
+ ParsedPackage parsedPackage = request.getParsedPackage();
+ if (parsedPackage != null && "android".equals(parsedPackage.getPackageName())) {
// TODO(b/135203078): Move this to initial parse
parsedPackage.setVersionCode(mPm.getSdkVersion())
.setVersionCodeMajor(0);
}
- final AndroidPackage oldPkg = request.mOldPkg;
- final @ParsingPackageUtils.ParseFlags int parseFlags = request.mParseFlags;
- final @PackageManagerService.ScanFlags int scanFlags = request.mScanFlags;
- final PackageSetting oldPkgSetting = request.mOldPkgSetting;
- final PackageSetting originalPkgSetting = request.mOriginalPkgSetting;
- final UserHandle user = request.mUser;
- final String realPkgName = request.mRealPkgName;
- final List<String> changedAbiCodePath = result.mChangedAbiCodePath;
+ final @PackageManagerService.ScanFlags int scanFlags = request.getScanFlags();
+ final PackageSetting oldPkgSetting = request.getScanRequestOldPackageSetting();
+ final PackageSetting originalPkgSetting = request.getScanRequestOriginalPackageSetting();
+ final String realPkgName = request.getRealPackageName();
+ final List<String> changedAbiCodePath = request.getChangedAbiCodePath();
final PackageSetting pkgSetting;
- if (request.mPkgSetting != null) {
+ if (request.getScanRequestPackageSetting() != null) {
SharedUserSetting requestSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(
- request.mPkgSetting);
+ request.getScanRequestPackageSetting());
SharedUserSetting resultSharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(
- result.mPkgSetting);
+ request.getScanRequestPackageSetting());
if (requestSharedUserSetting != null
&& requestSharedUserSetting != resultSharedUserSetting) {
// shared user changed, remove from old shared user
- requestSharedUserSetting.removePackage(request.mPkgSetting);
+ requestSharedUserSetting.removePackage(request.getScanRequestPackageSetting());
// Prune unused SharedUserSetting
if (mPm.mSettings.checkAndPruneSharedUserLPw(requestSharedUserSetting, false)) {
// Set the app ID in removed info for UID_REMOVED broadcasts
- if (reconciledPkg.mInstallRequest != null
- && reconciledPkg.mInstallRequest.getRemovedInfo() != null) {
- reconciledPkg.mInstallRequest.getRemovedInfo().mRemovedAppId =
- requestSharedUserSetting.mAppId;
- }
+ request.setRemovedAppId(requestSharedUserSetting.mAppId);
}
}
}
- if (result.mExistingSettingCopied) {
- pkgSetting = request.mPkgSetting;
- pkgSetting.updateFrom(result.mPkgSetting);
+ if (request.isExistingSettingCopied()) {
+ pkgSetting = request.getScanRequestPackageSetting();
+ pkgSetting.updateFrom(request.getScannedPackageSetting());
} else {
- pkgSetting = result.mPkgSetting;
+ pkgSetting = request.getScannedPackageSetting();
if (originalPkgSetting != null) {
mPm.mSettings.addRenamedPackageLPw(
AndroidPackageUtils.getRealPackageOrNull(parsedPackage),
@@ -308,26 +300,23 @@ final class InstallPackageHelper {
mPm.mSettings.convertSharedUserSettingsLPw(sharedUserSetting);
}
}
- if (reconciledPkg.mInstallRequest != null
- && reconciledPkg.mInstallRequest.isForceQueryableOverride()) {
+ if (request.isForceQueryableOverride()) {
pkgSetting.setForceQueryableOverride(true);
}
// If this is part of a standard install, set the initiating package name, else rely on
// previous device state.
- if (reconciledPkg.mInstallRequest != null) {
- InstallSource installSource = reconciledPkg.mInstallRequest.getInstallSource();
- if (installSource != null) {
- if (installSource.initiatingPackageName != null) {
- final PackageSetting ips = mPm.mSettings.getPackageLPr(
- installSource.initiatingPackageName);
- if (ips != null) {
- installSource = installSource.setInitiatingPackageSignatures(
- ips.getSignatures());
- }
+ InstallSource installSource = request.getInstallSource();
+ if (installSource != null) {
+ if (installSource.initiatingPackageName != null) {
+ final PackageSetting ips = mPm.mSettings.getPackageLPr(
+ installSource.initiatingPackageName);
+ if (ips != null) {
+ installSource = installSource.setInitiatingPackageSignatures(
+ ips.getSignatures());
}
- pkgSetting.setInstallSource(installSource);
}
+ pkgSetting.setInstallSource(installSource);
}
if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
@@ -382,10 +371,9 @@ final class InstallPackageHelper {
}
}
- final int userId = user == null ? 0 : user.getIdentifier();
+ final int userId = request.getUserId();
// Modify state for the given package setting
- commitPackageSettings(pkg, oldPkg, pkgSetting, oldPkgSetting, scanFlags,
- (parseFlags & ParsingPackageUtils.PARSE_CHATTY) != 0 /*chatty*/, reconciledPkg);
+ commitPackageSettings(pkg, pkgSetting, oldPkgSetting, reconciledPkg);
if (pkgSetting.getInstantApp(userId)) {
mPm.mInstantAppRegistry.addInstantApp(userId, pkgSetting.getAppId());
}
@@ -401,11 +389,14 @@ final class InstallPackageHelper {
* Adds a scanned package to the system. When this method is finished, the package will
* be available for query, resolution, etc...
*/
- private void commitPackageSettings(@NonNull AndroidPackage pkg, @Nullable AndroidPackage oldPkg,
+ private void commitPackageSettings(@NonNull AndroidPackage pkg,
@NonNull PackageSetting pkgSetting, @Nullable PackageSetting oldPkgSetting,
- final @PackageManagerService.ScanFlags int scanFlags, boolean chatty,
ReconciledPackage reconciledPkg) {
final String pkgName = pkg.getPackageName();
+ final InstallRequest request = reconciledPkg.mInstallRequest;
+ final AndroidPackage oldPkg = request.getScanRequestOldPackage();
+ final int scanFlags = request.getScanFlags();
+ final boolean chatty = (request.getParseFlags() & ParsingPackageUtils.PARSE_CHATTY) != 0;
if (mPm.mCustomResolverComponentName != null
&& mPm.mCustomResolverComponentName.getPackageName().equals(pkg.getPackageName())) {
mPm.setUpCustomResolverActivity(pkg, pkgSetting);
@@ -421,9 +412,7 @@ final class InstallPackageHelper {
reconciledPkg.mAllowedSharedLibraryInfos,
reconciledPkg.getCombinedAvailablePackages(), scanFlags);
- if (reconciledPkg.mInstallRequest != null) {
- reconciledPkg.mInstallRequest.setLibraryConsumers(clientLibPkgs);
- }
+ request.setLibraryConsumers(clientLibPkgs);
if ((scanFlags & SCAN_BOOTING) != 0) {
// No apps can run during boot scan, so they don't need to be frozen
@@ -438,8 +427,7 @@ final class InstallPackageHelper {
mPm.snapshotComputer().checkPackageFrozen(pkgName);
}
- final boolean isReplace =
- reconciledPkg.mPrepareResult != null && reconciledPkg.mPrepareResult.mReplace;
+ final boolean isReplace = request.isReplace();
// Also need to kill any apps that are dependent on the library, except the case of
// installation of new version static shared library.
if (clientLibPkgs != null) {
@@ -705,6 +693,9 @@ final class InstallPackageHelper {
* Returns whether the restore successfully completed.
*/
private boolean performBackupManagerRestore(int userId, int token, InstallRequest request) {
+ if (request.getPkg() == null) {
+ return false;
+ }
IBackupManager iBackupManager = mInjector.getIBackupManager();
if (iBackupManager != null) {
// For backwards compatibility as USER_ALL previously routed directly to USER_SYSTEM
@@ -743,6 +734,9 @@ final class InstallPackageHelper {
* Returns whether the restore successfully completed.
*/
private boolean performRollbackManagerRestore(int userId, int token, InstallRequest request) {
+ if (request.getPkg() == null) {
+ return false;
+ }
final String packageName = request.getPkg().getPackageName();
final int[] allUsers = mPm.mUserManager.getUserIds();
final int[] installedUsers;
@@ -810,22 +804,16 @@ final class InstallPackageHelper {
*/
@GuardedBy("mPm.mInstallLock")
private void installPackagesLI(List<InstallRequest> requests) {
- final Map<String, ScanResult> preparedScans = new ArrayMap<>(requests.size());
- final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
- final Map<String, InstallRequest> installRequests = new ArrayMap<>(requests.size());
+ final Set<String> scannedPackages = new ArraySet<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
boolean success = false;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
for (InstallRequest request : requests) {
- // TODO(b/109941548): remove this once we've pulled everything from it and into
- // scan, reconcile or commit.
- final PrepareResult prepareResult;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
- prepareResult =
- preparePackageLI(request);
+ preparePackageLI(request);
} catch (PrepareFailure prepareFailure) {
request.setError(prepareFailure.error,
prepareFailure.getMessage());
@@ -835,28 +823,31 @@ final class InstallPackageHelper {
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
- request.setInstallerPackageName(request.getSourceInstallerPackageName());
- final String packageName = prepareResult.mPackageToScan.getPackageName();
- prepareResults.put(packageName, prepareResult);
- installRequests.put(packageName, request);
+ final ParsedPackage packageToScan = request.getParsedPackage();
+ if (packageToScan == null) {
+ request.setError(INSTALL_FAILED_SESSION_INVALID,
+ "Failed to obtain package to scan");
+ return;
+ }
+ request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
+ final String packageName = packageToScan.getPackageName();
try {
- final ScanResult result = scanPackageTracedLI(
- prepareResult.mPackageToScan, prepareResult.mParseFlags,
- prepareResult.mScanFlags, System.currentTimeMillis(),
- request.getUser(), request.getAbiOverride());
- if (null != preparedScans.put(result.mPkgSetting.getPkg().getPackageName(),
- result)) {
+ final ScanResult scanResult = scanPackageTracedLI(request.getParsedPackage(),
+ request.getParseFlags(), request.getScanFlags(),
+ System.currentTimeMillis(), request.getUser(),
+ request.getAbiOverride());
+ request.setScanResult(scanResult);
+ if (!scannedPackages.add(packageName)) {
request.setError(
PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE,
"Duplicate package "
- + result.mPkgSetting.getPkg().getPackageName()
+ + packageName
+ " in multi-package install request.");
return;
}
if (!checkNoAppStorageIsConsistent(
- result.mRequest.mOldPkg, result.mPkgSetting.getPkg())) {
+ request.getScanRequestOldPackage(), packageToScan)) {
// TODO: INSTALL_FAILED_UPDATE_INCOMPATIBLE is about incomptabible
// signatures. Is there a better error code?
request.setError(
@@ -865,31 +856,28 @@ final class InstallPackageHelper {
+ PackageManager.PROPERTY_NO_APP_DATA_STORAGE);
return;
}
- final boolean isApex = (result.mRequest.mScanFlags & SCAN_AS_APEX) != 0;
+ final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
if (!isApex) {
- createdAppId.put(packageName, optimisticallyRegisterAppId(result));
+ createdAppId.put(packageName, optimisticallyRegisterAppId(request));
} else {
- result.mPkgSetting.setAppId(Process.INVALID_UID);
+ request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
}
- versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
- mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
+ versionInfos.put(packageName,
+ mPm.getSettingsVersionForPackage(packageToScan));
} catch (PackageManagerException e) {
request.setError("Scanning Failed.", e);
return;
}
}
- CommitRequest commitRequest;
+ Map<String, ReconciledPackage> reconciledPackages;
synchronized (mPm.mLock) {
- ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans,
- installRequests, prepareResults,
- Collections.unmodifiableMap(mPm.mPackages), versionInfos);
- Map<String, ReconciledPackage> reconciledPackages;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
reconciledPackages = ReconcilePackageUtils.reconcilePackages(
- reconcileRequest, mSharedLibraries,
- mPm.mSettings.getKeySetManagerService(), mPm.mSettings);
+ requests, Collections.unmodifiableMap(mPm.mPackages),
+ versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
+ mPm.mSettings);
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.setError("Reconciliation failed...", e);
@@ -900,15 +888,13 @@ final class InstallPackageHelper {
}
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "commitPackages");
- commitRequest = new CommitRequest(reconciledPackages,
- mPm.mUserManager.getUserIds());
- commitPackagesLocked(commitRequest);
+ commitPackagesLocked(reconciledPackages, mPm.mUserManager.getUserIds());
success = true;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
- executePostCommitStepsLIF(commitRequest);
+ executePostCommitStepsLIF(reconciledPackages);
} finally {
if (success) {
for (InstallRequest request : requests) {
@@ -932,10 +918,10 @@ final class InstallPackageHelper {
request.getDataLoaderType(), request.getUser(), mContext);
}
} else {
- for (ScanResult result : preparedScans.values()) {
- if (createdAppId.getOrDefault(result.mRequest.mParsedPackage.getPackageName(),
- false)) {
- cleanUpAppIdCreation(result);
+ for (InstallRequest installRequest : requests) {
+ if (installRequest.getParsedPackage() != null && createdAppId.getOrDefault(
+ installRequest.getParsedPackage().getPackageName(), false)) {
+ cleanUpAppIdCreation(installRequest);
}
}
// TODO(b/194319951): create a more descriptive reason than unknown
@@ -968,8 +954,7 @@ final class InstallPackageHelper {
}
@GuardedBy("mPm.mInstallLock")
- private PrepareResult preparePackageLI(InstallRequest request)
- throws PrepareFailure {
+ private void preparePackageLI(InstallRequest request) throws PrepareFailure {
final int installFlags = request.getInstallFlags();
final boolean onExternal = request.getVolumeUuid() != null;
final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
@@ -1550,8 +1535,7 @@ final class InstallPackageHelper {
// don't allow an upgrade from full to ephemeral
if (isInstantApp) {
- if (request.getUser() == null
- || request.getUserId() == UserHandle.USER_ALL) {
+ if (request.getUserId() == UserHandle.USER_ALL) {
for (int currentUser : allUsers) {
if (!ps.getInstantApp(currentUser)) {
// can't downgrade from full to instant
@@ -1624,7 +1608,6 @@ final class InstallPackageHelper {
targetParseFlags = systemParseFlags;
targetScanFlags = systemScanFlags;
} else { // non system replace
- replace = true;
if (DEBUG_INSTALL) {
Slog.d(TAG,
"replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
@@ -1634,7 +1617,6 @@ final class InstallPackageHelper {
} else { // new package install
ps = null;
disabledPs = null;
- replace = false;
oldPackage = null;
// Remember this for later, in case we need to rollback this install
String pkgName1 = parsedPackage.getPackageName();
@@ -1665,7 +1647,7 @@ final class InstallPackageHelper {
// we're passing the freezer back to be closed in a later phase of install
shouldCloseFreezerBeforeReturn = false;
- return new PrepareResult(replace, targetScanFlags, targetParseFlags,
+ request.setPrepareResult(replace, targetScanFlags, targetParseFlags,
oldPackage, parsedPackage, replace /* clearCodeCache */, sysPkg,
ps, disabledPs);
} finally {
@@ -1894,19 +1876,18 @@ final class InstallPackageHelper {
}
@GuardedBy("mPm.mLock")
- private void commitPackagesLocked(final CommitRequest request) {
+ private void commitPackagesLocked(Map<String, ReconciledPackage> reconciledPackages,
+ @NonNull int[] allUsers) {
// TODO: remove any expected failures from this method; this should only be able to fail due
// to unavoidable errors (I/O, etc.)
- for (ReconciledPackage reconciledPkg : request.mReconciledPackages.values()) {
- final ScanResult scanResult = reconciledPkg.mScanResult;
- final ScanRequest scanRequest = scanResult.mRequest;
- final ParsedPackage parsedPackage = scanRequest.mParsedPackage;
- final String packageName = parsedPackage.getPackageName();
+ for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
final InstallRequest installRequest = reconciledPkg.mInstallRequest;
+ final ParsedPackage parsedPackage = installRequest.getParsedPackage();
+ final String packageName = parsedPackage.getPackageName();
final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
final DeletePackageHelper deletePackageHelper = new DeletePackageHelper(mPm);
- if (reconciledPkg.mPrepareResult.mReplace) {
+ if (installRequest.isReplace()) {
AndroidPackage oldPackage = mPm.mPackages.get(packageName);
// Set the update and install times
@@ -1914,15 +1895,16 @@ final class InstallPackageHelper {
.getPackageStateInternal(oldPackage.getPackageName());
// TODO(b/225756739): For rebootless APEX, consider using lastUpdateMillis provided
// by apexd to be more accurate.
- reconciledPkg.mPkgSetting
- .setFirstInstallTimeFromReplaced(deletedPkgSetting, request.mAllUsers)
- .setLastUpdateTime(System.currentTimeMillis());
+ installRequest.setScannedPackageSettingFirstInstallTimeFromReplaced(
+ deletedPkgSetting, allUsers);
+ installRequest.setScannedPackageSettingLastUpdateTime(
+ System.currentTimeMillis());
installRequest.getRemovedInfo().mBroadcastAllowList =
mPm.mAppsFilter.getVisibilityAllowList(mPm.snapshotComputer(),
- reconciledPkg.mPkgSetting, request.mAllUsers,
- mPm.mSettings.getPackagesLocked());
- if (reconciledPkg.mPrepareResult.mSystem) {
+ installRequest.getScannedPackageSetting(),
+ allUsers, mPm.mSettings.getPackagesLocked());
+ if (installRequest.isSystem()) {
// Remove existing system package
removePackageHelper.removePackage(oldPackage, true);
if (!disableSystemPackageLPw(oldPackage)) {
@@ -1942,7 +1924,7 @@ final class InstallPackageHelper {
// Settings will be written during the call to updateSettingsLI().
deletePackageHelper.executeDeletePackage(
reconciledPkg.mDeletePackageAction, packageName,
- true, request.mAllUsers, false);
+ true, allUsers, false);
} catch (SystemDeleteException e) {
if (mPm.mIsEngBuild) {
throw new RuntimeException("Unexpected failure", e);
@@ -1952,7 +1934,7 @@ final class InstallPackageHelper {
// Successfully deleted the old package; proceed with replace.
// Update the in-memory copy of the previous code paths.
PackageSetting ps1 = mPm.mSettings.getPackageLPr(
- reconciledPkg.mPrepareResult.mExistingPackage.getPackageName());
+ installRequest.getExistingPackageName());
if ((installRequest.getInstallFlags() & PackageManager.DONT_KILL_APP)
== 0) {
Set<String> oldCodePaths = ps1.getOldCodePaths();
@@ -1977,9 +1959,8 @@ final class InstallPackageHelper {
}
}
- AndroidPackage pkg = commitReconciledScanResultLocked(
- reconciledPkg, request.mAllUsers);
- updateSettingsLI(pkg, reconciledPkg, request.mAllUsers, installRequest);
+ AndroidPackage pkg = commitReconciledScanResultLocked(reconciledPkg, allUsers);
+ updateSettingsLI(pkg, allUsers, installRequest);
final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
if (ps != null) {
@@ -2000,12 +1981,12 @@ final class InstallPackageHelper {
return mPm.mSettings.disableSystemPackageLPw(oldPkg.getPackageName(), true);
}
- private void updateSettingsLI(AndroidPackage newPackage, ReconciledPackage reconciledPkg,
+ private void updateSettingsLI(AndroidPackage newPackage,
int[] allUsers, InstallRequest installRequest) {
- updateSettingsInternalLI(newPackage, reconciledPkg, allUsers, installRequest);
+ updateSettingsInternalLI(newPackage, allUsers, installRequest);
}
- private void updateSettingsInternalLI(AndroidPackage pkg, ReconciledPackage reconciledPkg,
+ private void updateSettingsInternalLI(AndroidPackage pkg,
int[] allUsers, InstallRequest installRequest) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
@@ -2175,8 +2156,7 @@ final class InstallPackageHelper {
}
final int autoRevokePermissionsMode = installRequest.getAutoRevokePermissionsMode();
permissionParamsBuilder.setAutoRevokePermissionsMode(autoRevokePermissionsMode);
- final ScanResult scanResult = reconciledPkg.mScanResult;
- mPm.mPermissionManager.onPackageInstalled(pkg, scanResult.mPreviousAppId,
+ mPm.mPermissionManager.onPackageInstalled(pkg, installRequest.getPreviousAppId(),
permissionParamsBuilder.build(), userId);
// Apply restricted settings on potentially dangerous packages.
if (installRequest.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
@@ -2216,14 +2196,13 @@ final class InstallPackageHelper {
* locks on {@link com.android.server.pm.PackageManagerService.mLock}.
*/
@GuardedBy("mPm.mInstallLock")
- private void executePostCommitStepsLIF(CommitRequest commitRequest) {
+ private void executePostCommitStepsLIF(Map<String, ReconciledPackage> reconciledPackages) {
final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
- for (ReconciledPackage reconciledPkg : commitRequest.mReconciledPackages.values()) {
- final boolean instantApp = ((reconciledPkg.mScanResult.mRequest.mScanFlags
- & SCAN_AS_INSTANT_APP) != 0);
- final boolean isApex = ((reconciledPkg.mScanResult.mRequest.mScanFlags
- & SCAN_AS_APEX) != 0);
- final AndroidPackage pkg = reconciledPkg.mPkgSetting.getPkg();
+ for (ReconciledPackage reconciledPkg : reconciledPackages.values()) {
+ final InstallRequest installRequest = reconciledPkg.mInstallRequest;
+ final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
+ final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
+ final AndroidPackage pkg = installRequest.getScannedPackageSetting().getPkg();
final String packageName = pkg.getPackageName();
final String codePath = pkg.getPath();
final boolean onIncremental = mIncrementalManager != null
@@ -2238,12 +2217,12 @@ final class InstallPackageHelper {
}
// Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
- if (reconciledPkg.mPrepareResult.mClearCodeCache) {
+ if (installRequest.isClearCodeCache()) {
mAppDataHelper.clearAppDataLIF(pkg, UserHandle.USER_ALL,
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
| Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
}
- if (reconciledPkg.mPrepareResult.mReplace) {
+ if (installRequest.isReplace()) {
mDexManager.notifyPackageUpdated(pkg.getPackageName(),
pkg.getBaseApkPath(), pkg.getSplitCodePaths());
}
@@ -2252,14 +2231,13 @@ final class InstallPackageHelper {
// This needs to be done before invoking dexopt so that any install-time profile
// can be used for optimizations.
mArtManagerService.prepareAppProfiles(
- pkg,
- mPm.resolveUserIds(reconciledPkg.mInstallRequest.getUserId()),
+ pkg, mPm.resolveUserIds(installRequest.getUserId()),
/* updateReferenceProfileContent= */ true);
// Compute the compilation reason from the installation scenario.
final int compilationReason =
mDexManager.getCompilationReasonForInstallScenario(
- reconciledPkg.mInstallRequest.getInstallScenario());
+ installRequest.getInstallScenario());
// Construct the DexoptOptions early to see if we should skip running dexopt.
//
@@ -2268,10 +2246,8 @@ final class InstallPackageHelper {
//
// Also, don't fail application installs if the dexopt step fails.
final boolean isBackupOrRestore =
- reconciledPkg.mInstallRequest.getInstallReason()
- == INSTALL_REASON_DEVICE_RESTORE
- || reconciledPkg.mInstallRequest.getInstallReason()
- == INSTALL_REASON_DEVICE_SETUP;
+ installRequest.getInstallReason() == INSTALL_REASON_DEVICE_RESTORE
+ || installRequest.getInstallReason() == INSTALL_REASON_DEVICE_SETUP;
final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
| DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE
@@ -2323,22 +2299,14 @@ final class InstallPackageHelper {
}
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
- ScanResult result = reconciledPkg.mScanResult;
// This mirrors logic from commitReconciledScanResultLocked, where the library files
// needed for dexopt are assigned.
- // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous
- // setting needs to be passed to have a comparison, hide it behind an immutable
- // interface. There's no good reason to have 3 different ways to access the real
- // PackageSetting object, only one of which is actually correct.
- PackageSetting realPkgSetting = result.mExistingSettingCopied
- ? result.mRequest.mPkgSetting : result.mPkgSetting;
- if (realPkgSetting == null) {
- realPkgSetting = reconciledPkg.mPkgSetting;
- }
+ PackageSetting realPkgSetting = installRequest.getRealPackageSetting();
// Unfortunately, the updated system app flag is only tracked on this PackageSetting
- boolean isUpdatedSystemApp = reconciledPkg.mPkgSetting.getPkgState()
+ boolean isUpdatedSystemApp =
+ installRequest.getScannedPackageSetting().getPkgState()
.isUpdatedSystemApp();
realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
@@ -3059,7 +3027,7 @@ final class InstallPackageHelper {
final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm);
removePackageHelper.removePackage(stubPkg, true /*chatty*/);
try {
- return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null);
+ return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags);
} catch (PackageManagerException e) {
Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(),
e);
@@ -3191,7 +3159,7 @@ final class InstallPackageHelper {
| ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
@PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath);
final AndroidPackage pkg = scanSystemPackageTracedLI(
- codePath, parseFlags, scanFlags, null);
+ codePath, parseFlags, scanFlags);
synchronized (mPm.mLock) {
PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName());
@@ -3371,7 +3339,7 @@ final class InstallPackageHelper {
try {
final File codePath = new File(pkg.getPath());
synchronized (mPm.mInstallLock) {
- scanSystemPackageTracedLI(codePath, 0, scanFlags, null);
+ scanSystemPackageTracedLI(codePath, 0, scanFlags);
}
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse updated, ex-system package: "
@@ -3521,7 +3489,7 @@ final class InstallPackageHelper {
}
try {
addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
- null);
+ new UserHandle(UserHandle.USER_SYSTEM));
} catch (PackageManagerException e) {
errorCode = e.error;
errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();
@@ -3584,7 +3552,7 @@ final class InstallPackageHelper {
try {
synchronized (mPm.mInstallLock) {
final AndroidPackage newPkg = scanSystemPackageTracedLI(
- scanFile, reparseFlags, rescanFlags, null);
+ scanFile, reparseFlags, rescanFlags);
// We rescanned a stub, add it to the list of stubbed system packages
if (newPkg.isStub()) {
stubSystemApps.add(packageName);
@@ -3603,10 +3571,10 @@ final class InstallPackageHelper {
*/
@GuardedBy("mPm.mInstallLock")
public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags,
- int scanFlags, UserHandle user) throws PackageManagerException {
+ int scanFlags) throws PackageManagerException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]");
try {
- return scanSystemPackageLI(scanFile, parseFlags, scanFlags, user);
+ return scanSystemPackageLI(scanFile, parseFlags, scanFlags);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -3617,8 +3585,8 @@ final class InstallPackageHelper {
* Returns {@code null} in case of errors and the error code is stored in mLastScanError
*/
@GuardedBy("mPm.mInstallLock")
- private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags,
- UserHandle user) throws PackageManagerException {
+ private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags)
+ throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
@@ -3634,7 +3602,8 @@ final class InstallPackageHelper {
PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage);
}
- return addForInitLI(parsedPackage, parseFlags, scanFlags, user);
+ return addForInitLI(parsedPackage, parseFlags, scanFlags,
+ new UserHandle(UserHandle.USER_SYSTEM));
}
/**
@@ -3660,33 +3629,32 @@ final class InstallPackageHelper {
parsedPackage, parseFlags, scanFlags, user);
final ScanResult scanResult = scanResultPair.first;
boolean shouldHideSystemApp = scanResultPair.second;
- if (scanResult.mSuccess) {
- synchronized (mPm.mLock) {
- boolean appIdCreated = false;
- try {
- final String pkgName = scanResult.mPkgSetting.getPackageName();
- final ReconcileRequest reconcileRequest = new ReconcileRequest(
- Collections.singletonMap(pkgName, scanResult),
- mPm.mPackages,
- Collections.singletonMap(pkgName,
- mPm.getSettingsVersionForPackage(parsedPackage)));
- final Map<String, ReconciledPackage> reconcileResult =
- ReconcilePackageUtils.reconcilePackages(reconcileRequest,
- mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
- if ((scanFlags & SCAN_AS_APEX) == 0) {
- appIdCreated = optimisticallyRegisterAppId(scanResult);
- } else {
- scanResult.mPkgSetting.setAppId(Process.INVALID_UID);
- }
- commitReconciledScanResultLocked(reconcileResult.get(pkgName),
- mPm.mUserManager.getUserIds());
- } catch (PackageManagerException e) {
- if (appIdCreated) {
- cleanUpAppIdCreation(scanResult);
- }
- throw e;
+ final InstallRequest installRequest = new InstallRequest(
+ parsedPackage, parseFlags, scanFlags, user, scanResult);
+
+ synchronized (mPm.mLock) {
+ boolean appIdCreated = false;
+ try {
+ final String pkgName = scanResult.mPkgSetting.getPackageName();
+ final Map<String, ReconciledPackage> reconcileResult =
+ ReconcilePackageUtils.reconcilePackages(
+ Collections.singletonList(installRequest),
+ mPm.mPackages, Collections.singletonMap(pkgName,
+ mPm.getSettingsVersionForPackage(parsedPackage)),
+ mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
+ mPm.mSettings);
+ if ((scanFlags & SCAN_AS_APEX) == 0) {
+ appIdCreated = optimisticallyRegisterAppId(installRequest);
+ } else {
+ installRequest.setScannedPackageSettingAppId(Process.INVALID_UID);
}
+ commitReconciledScanResultLocked(reconcileResult.get(pkgName),
+ mPm.mUserManager.getUserIds());
+ } catch (PackageManagerException e) {
+ if (appIdCreated) {
+ cleanUpAppIdCreation(installRequest);
+ }
+ throw e;
}
}
@@ -3711,13 +3679,14 @@ final class InstallPackageHelper {
* @return {@code true} if a new app ID was registered and will need to be cleaned up on
* failure.
*/
- private boolean optimisticallyRegisterAppId(@NonNull ScanResult result)
+ private boolean optimisticallyRegisterAppId(@NonNull InstallRequest installRequest)
throws PackageManagerException {
- if (!result.mExistingSettingCopied || result.needsNewAppId()) {
+ if (!installRequest.isExistingSettingCopied() || installRequest.needsNewAppId()) {
synchronized (mPm.mLock) {
// THROWS: when we can't allocate a user id. add call to check if there's
// enough space to ensure we won't throw; otherwise, don't modify state
- return mPm.mSettings.registerAppIdLPw(result.mPkgSetting, result.needsNewAppId());
+ return mPm.mSettings.registerAppIdLPw(installRequest.getScannedPackageSetting(),
+ installRequest.needsNewAppId());
}
}
return false;
@@ -3725,15 +3694,16 @@ final class InstallPackageHelper {
/**
* Reverts any app ID creation that were made by
- * {@link #optimisticallyRegisterAppId(ScanResult)}. Note: this is only necessary if the
+ * {@link #optimisticallyRegisterAppId(InstallRequest)}. Note: this is only necessary if the
* referenced method returned true.
*/
- private void cleanUpAppIdCreation(@NonNull ScanResult result) {
+ private void cleanUpAppIdCreation(@NonNull InstallRequest installRequest) {
// iff we've acquired an app ID for a new package setting, remove it so that it can be
// acquired by another request.
- if (result.mPkgSetting.getAppId() > 0) {
+ if (installRequest.getScannedPackageSetting() != null
+ && installRequest.getScannedPackageSetting().getAppId() > 0) {
synchronized (mPm.mLock) {
- mPm.mSettings.removeAppIdLPw(result.mPkgSetting.getAppId());
+ mPm.mSettings.removeAppIdLPw(installRequest.getScannedPackageSetting().getAppId());
}
}
}
@@ -3857,7 +3827,6 @@ final class InstallPackageHelper {
}
}
- @GuardedBy("mPm.mInstallLock")
private Pair<ScanResult, Boolean> scanSystemPackageLI(ParsedPackage parsedPackage,
@ParsingPackageUtils.ParseFlags int parseFlags,
@PackageManagerService.ScanFlags int scanFlags,
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 36bbf41bffe6..573082a58255 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
+import static android.os.Process.INVALID_UID;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -29,13 +30,19 @@ import android.content.pm.DataLoaderType;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.net.Uri;
+import android.os.Build;
+import android.os.Process;
import android.os.UserHandle;
import android.util.ExceptionUtils;
import android.util.Slog;
+import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import java.io.File;
import java.util.ArrayList;
@@ -45,13 +52,59 @@ final class InstallRequest {
private final int mUserId;
@Nullable
private final InstallArgs mInstallArgs;
- @NonNull
- private final PackageInstalledInfo mInstalledInfo;
@Nullable
private Runnable mPostInstallRunnable;
@Nullable
private PackageRemovedInfo mRemovedInfo;
+ private @PackageManagerService.ScanFlags int mScanFlags;
+ private @ParsingPackageUtils.ParseFlags int mParseFlags;
+ private boolean mReplace;
+
+ @Nullable /* The original Package if it is being replaced, otherwise {@code null} */
+ private AndroidPackage mExistingPackage;
+ /** parsed package to be scanned */
+ @Nullable
+ private ParsedPackage mParsedPackage;
+ private boolean mClearCodeCache;
+ private boolean mSystem;
+ @Nullable
+ private PackageSetting mOriginalPs;
+ @Nullable
+ private PackageSetting mDisabledPs;
+
+ /** Package Installed Info */
+ @Nullable
+ private String mName;
+ private int mUid = -1;
+ // The set of users that originally had this package installed.
+ @Nullable
+ private int[] mOrigUsers;
+ // The set of users that now have this package installed.
+ @Nullable
+ private int[] mNewUsers;
+ @Nullable
+ private AndroidPackage mPkg;
+ private int mReturnCode;
+ @Nullable
+ private String mReturnMsg;
+ // The set of packages consuming this shared library or null if no consumers exist.
+ @Nullable
+ private ArrayList<AndroidPackage> mLibraryConsumers;
+ @Nullable
+ private PackageFreezer mFreezer;
+ /** The package this package replaces */
+ @Nullable
+ private String mOrigPackage;
+ @Nullable
+ private String mOrigPermission;
+ // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install
+ @Nullable
+ private ApexInfo mApexInfo;
+
+ @Nullable
+ private ScanResult mScanResult;
+
// New install
InstallRequest(InstallingSession params) {
mUserId = params.getUser().getIdentifier();
@@ -63,7 +116,6 @@ final class InstallRequest {
params.mTraceMethod, params.mTraceCookie, params.mSigningDetails,
params.mInstallReason, params.mInstallScenario, params.mForceQueryableOverride,
params.mDataLoaderType, params.mPackageSource);
- mInstalledInfo = new PackageInstalledInfo();
}
// Install existing package as user
@@ -71,56 +123,56 @@ final class InstallRequest {
Runnable runnable) {
mUserId = userId;
mInstallArgs = null;
- mInstalledInfo = new PackageInstalledInfo();
- mInstalledInfo.mReturnCode = returnCode;
- mInstalledInfo.mPkg = pkg;
- mInstalledInfo.mNewUsers = newUsers;
+ mReturnCode = returnCode;
+ mPkg = pkg;
+ mNewUsers = newUsers;
mPostInstallRunnable = runnable;
}
- private static class PackageInstalledInfo {
- String mName;
- int mUid = -1;
- // The set of users that originally had this package installed.
- int[] mOrigUsers;
- // The set of users that now have this package installed.
- int[] mNewUsers;
- AndroidPackage mPkg;
- int mReturnCode;
- String mReturnMsg;
- String mInstallerPackageName;
- // The set of packages consuming this shared library or null if no consumers exist.
- ArrayList<AndroidPackage> mLibraryConsumers;
- PackageFreezer mFreezer;
- // In some error cases we want to convey more info back to the observer
- String mOrigPackage;
- String mOrigPermission;
- // The ApexInfo returned by ApexManager#installPackage, used by rebootless APEX install
- ApexInfo mApexInfo;
+ // addForInit
+ InstallRequest(ParsedPackage parsedPackage, int parseFlags, int scanFlags,
+ @Nullable UserHandle user, ScanResult scanResult) {
+ if (user != null) {
+ mUserId = user.getIdentifier();
+ } else {
+ // APEX
+ mUserId = INVALID_UID;
+ }
+ mInstallArgs = null;
+ mParsedPackage = parsedPackage;
+ mParseFlags = parseFlags;
+ mScanFlags = scanFlags;
+ mScanResult = scanResult;
}
+ @Nullable
public String getName() {
- return mInstalledInfo.mName;
+ return mName;
}
+ @Nullable
public String getReturnMsg() {
- return mInstalledInfo.mReturnMsg;
+ return mReturnMsg;
}
+ @Nullable
public OriginInfo getOriginInfo() {
return mInstallArgs == null ? null : mInstallArgs.mOriginInfo;
}
+ @Nullable
public PackageRemovedInfo getRemovedInfo() {
return mRemovedInfo;
}
+ @Nullable
public String getOrigPackage() {
- return mInstalledInfo.mOrigPackage;
+ return mOrigPackage;
}
+ @Nullable
public String getOrigPermission() {
- return mInstalledInfo.mOrigPermission;
+ return mOrigPermission;
}
@Nullable
@@ -140,7 +192,7 @@ final class InstallRequest {
}
public int getReturnCode() {
- return mInstalledInfo.mReturnCode;
+ return mReturnCode;
}
@Nullable
@@ -160,13 +212,13 @@ final class InstallRequest {
@Nullable
public String getMovePackageName() {
- return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
+ return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
? mInstallArgs.mMoveInfo.mPackageName : null;
}
@Nullable
public String getMoveFromCodePath() {
- return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
+ return (mInstallArgs != null && mInstallArgs.mMoveInfo != null)
? mInstallArgs.mMoveInfo.mFromCodePath : null;
}
@@ -203,8 +255,9 @@ final class InstallRequest {
return mInstallArgs == null ? null : mInstallArgs.mVolumeUuid;
}
+ @Nullable
public AndroidPackage getPkg() {
- return mInstalledInfo.mPkg;
+ return mPkg;
}
@Nullable
@@ -256,13 +309,15 @@ final class InstallRequest {
@Nullable
public Uri getOriginUri() {
- return mInstallArgs == null ? null : Uri.fromFile(mInstallArgs.mOriginInfo.mResolvedFile);
+ return mInstallArgs == null ? null : Uri.fromFile(mInstallArgs.mOriginInfo.mResolvedFile);
}
+ @Nullable
public ApexInfo getApexInfo() {
- return mInstalledInfo.mApexInfo;
+ return mApexInfo;
}
+ @Nullable
public String getSourceInstallerPackageName() {
return mInstallArgs.mInstallSource.installerPackageName;
}
@@ -272,25 +327,33 @@ final class InstallRequest {
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
}
+ @Nullable
public int[] getNewUsers() {
- return mInstalledInfo.mNewUsers;
+ return mNewUsers;
}
+ @Nullable
public int[] getOriginUsers() {
- return mInstalledInfo.mOrigUsers;
+ return mOrigUsers;
}
public int getUid() {
- return mInstalledInfo.mUid;
+ return mUid;
}
@Nullable
public String[] getInstallGrantPermissions() {
- return mInstallArgs == null ? null : mInstallArgs.mInstallGrantPermissions;
+ return mInstallArgs == null ? null : mInstallArgs.mInstallGrantPermissions;
}
+ @Nullable
public ArrayList<AndroidPackage> getLibraryConsumers() {
- return mInstalledInfo.mLibraryConsumers;
+ return mLibraryConsumers;
+ }
+
+ @Nullable
+ public AndroidPackage getExistingPackage() {
+ return mExistingPackage;
}
@Nullable
@@ -312,13 +375,170 @@ final class InstallRequest {
return mInstallArgs == null ? INSTALL_SCENARIO_DEFAULT : mInstallArgs.mInstallScenario;
}
+ @Nullable
+ public ParsedPackage getParsedPackage() {
+ return mParsedPackage;
+ }
+
+ public @ParsingPackageUtils.ParseFlags int getParseFlags() {
+ return mParseFlags;
+ }
+
+ public @PackageManagerService.ScanFlags int getScanFlags() {
+ return mScanFlags;
+ }
+
+ @Nullable
+ public String getExistingPackageName() {
+ if (mExistingPackage != null) {
+ return mExistingPackage.getPackageName();
+ }
+ return null;
+ }
+
+ @Nullable
+ public AndroidPackage getScanRequestOldPackage() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mOldPkg;
+ }
+
+ public boolean isClearCodeCache() {
+ return mClearCodeCache;
+ }
+
+ public boolean isReplace() {
+ return mReplace;
+ }
+
+ public boolean isSystem() {
+ return mSystem;
+ }
+
+ @Nullable
+ public PackageSetting getOriginalPackageSetting() {
+ return mOriginalPs;
+ }
+
+ @Nullable
+ public PackageSetting getDisabledPackageSetting() {
+ return mDisabledPs;
+ }
+
+ @Nullable
+ public PackageSetting getScanRequestOldPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mOldPkgSetting;
+ }
+
+ @Nullable
+ public PackageSetting getScanRequestOriginalPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mOriginalPkgSetting;
+ }
+
+ @Nullable
+ public PackageSetting getScanRequestPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mPkgSetting;
+ }
+
+ @Nullable
+ public String getRealPackageName() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mRealPkgName;
+ }
+
+ @Nullable
+ public List<String> getChangedAbiCodePath() {
+ assertScanResultExists();
+ return mScanResult.mChangedAbiCodePath;
+ }
+
public boolean isForceQueryableOverride() {
return mInstallArgs != null && mInstallArgs.mForceQueryableOverride;
}
+ @Nullable
+ public SharedLibraryInfo getSdkSharedLibraryInfo() {
+ assertScanResultExists();
+ return mScanResult.mSdkSharedLibraryInfo;
+ }
+
+ @Nullable
+ public SharedLibraryInfo getStaticSharedLibraryInfo() {
+ assertScanResultExists();
+ return mScanResult.mStaticSharedLibraryInfo;
+ }
+
+ @Nullable
+ public List<SharedLibraryInfo> getDynamicSharedLibraryInfos() {
+ assertScanResultExists();
+ return mScanResult.mDynamicSharedLibraryInfos;
+ }
+
+ @Nullable
+ public PackageSetting getScannedPackageSetting() {
+ assertScanResultExists();
+ return mScanResult.mPkgSetting;
+ }
+
+ @Nullable
+ public PackageSetting getRealPackageSetting() {
+ // TODO: Fix this to have 1 mutable PackageSetting for scan/install. If the previous
+ // setting needs to be passed to have a comparison, hide it behind an immutable
+ // interface. There's no good reason to have 3 different ways to access the real
+ // PackageSetting object, only one of which is actually correct.
+ PackageSetting realPkgSetting = isExistingSettingCopied()
+ ? getScanRequestPackageSetting() : getScannedPackageSetting();
+ if (realPkgSetting == null) {
+ realPkgSetting = getScannedPackageSetting();
+ }
+ return realPkgSetting;
+ }
+
+ public boolean isExistingSettingCopied() {
+ assertScanResultExists();
+ return mScanResult.mExistingSettingCopied;
+ }
+
+ /**
+ * Whether the original PackageSetting needs to be updated with
+ * a new app ID. Useful when leaving a sharedUserId.
+ */
+ public boolean needsNewAppId() {
+ assertScanResultExists();
+ return mScanResult.mPreviousAppId != Process.INVALID_UID;
+ }
+
+ public int getPreviousAppId() {
+ assertScanResultExists();
+ return mScanResult.mPreviousAppId;
+ }
+
+ public boolean isPlatformPackage() {
+ assertScanResultExists();
+ return mScanResult.mRequest.mIsPlatformPackage;
+ }
+
+ public void assertScanResultExists() {
+ if (mScanResult == null) {
+ // Should not happen. This indicates a bug in the installation code flow
+ if (Build.IS_USERDEBUG || Build.IS_ENG) {
+ throw new IllegalStateException("ScanResult cannot be null.");
+ } else {
+ Slog.e(TAG, "ScanResult is null and it should not happen");
+ }
+ }
+
+ }
+
+ public void setScanFlags(int scanFlags) {
+ mScanFlags = scanFlags;
+ }
+
public void closeFreezer() {
- if (mInstalledInfo.mFreezer != null) {
- mInstalledInfo.mFreezer.close();
+ if (mFreezer != null) {
+ mFreezer.close();
}
}
@@ -341,57 +561,53 @@ final class InstallRequest {
}
public void setError(String msg, PackageManagerException e) {
- mInstalledInfo.mReturnCode = e.error;
+ mReturnCode = e.error;
setReturnMessage(ExceptionUtils.getCompleteMessage(msg, e));
Slog.w(TAG, msg, e);
}
public void setReturnCode(int returnCode) {
- mInstalledInfo.mReturnCode = returnCode;
+ mReturnCode = returnCode;
}
public void setReturnMessage(String returnMsg) {
- mInstalledInfo.mReturnMsg = returnMsg;
+ mReturnMsg = returnMsg;
}
public void setApexInfo(ApexInfo apexInfo) {
- mInstalledInfo.mApexInfo = apexInfo;
+ mApexInfo = apexInfo;
}
public void setPkg(AndroidPackage pkg) {
- mInstalledInfo.mPkg = pkg;
+ mPkg = pkg;
}
public void setUid(int uid) {
- mInstalledInfo.mUid = uid;
+ mUid = uid;
}
public void setNewUsers(int[] newUsers) {
- mInstalledInfo.mNewUsers = newUsers;
+ mNewUsers = newUsers;
}
public void setOriginPackage(String originPackage) {
- mInstalledInfo.mOrigPackage = originPackage;
+ mOrigPackage = originPackage;
}
public void setOriginPermission(String originPermission) {
- mInstalledInfo.mOrigPermission = originPermission;
- }
-
- public void setInstallerPackageName(String installerPackageName) {
- mInstalledInfo.mInstallerPackageName = installerPackageName;
+ mOrigPermission = originPermission;
}
public void setName(String packageName) {
- mInstalledInfo.mName = packageName;
+ mName = packageName;
}
public void setOriginUsers(int[] userIds) {
- mInstalledInfo.mOrigUsers = userIds;
+ mOrigUsers = userIds;
}
public void setFreezer(PackageFreezer freezer) {
- mInstalledInfo.mFreezer = freezer;
+ mFreezer = freezer;
}
public void setRemovedInfo(PackageRemovedInfo removedInfo) {
@@ -399,6 +615,47 @@ final class InstallRequest {
}
public void setLibraryConsumers(ArrayList<AndroidPackage> libraryConsumers) {
- mInstalledInfo.mLibraryConsumers = libraryConsumers;
+ mLibraryConsumers = libraryConsumers;
+ }
+
+ public void setPrepareResult(boolean replace, int scanFlags,
+ int parseFlags, AndroidPackage existingPackage,
+ ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
+ PackageSetting originalPs, PackageSetting disabledPs) {
+ mReplace = replace;
+ mScanFlags = scanFlags;
+ mParseFlags = parseFlags;
+ mExistingPackage = existingPackage;
+ mParsedPackage = packageToScan;
+ mClearCodeCache = clearCodeCache;
+ mSystem = system;
+ mOriginalPs = originalPs;
+ mDisabledPs = disabledPs;
+ }
+
+ public void setScanResult(@NonNull ScanResult scanResult) {
+ mScanResult = scanResult;
+ }
+
+ public void setScannedPackageSettingAppId(int appId) {
+ assertScanResultExists();
+ mScanResult.mPkgSetting.setAppId(appId);
+ }
+
+ public void setScannedPackageSettingFirstInstallTimeFromReplaced(
+ @Nullable PackageStateInternal replacedPkgSetting, int[] userId) {
+ assertScanResultExists();
+ mScanResult.mPkgSetting.setFirstInstallTimeFromReplaced(replacedPkgSetting, userId);
+ }
+
+ public void setScannedPackageSettingLastUpdateTime(long lastUpdateTim) {
+ assertScanResultExists();
+ mScanResult.mPkgSetting.setLastUpdateTime(lastUpdateTim);
+ }
+
+ public void setRemovedAppId(int appId) {
+ if (mRemovedInfo != null) {
+ mRemovedInfo.mRemovedAppId = appId;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 8d5a5e156e71..16b3a815c075 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -42,7 +42,7 @@ import android.content.pm.parsing.PackageLite;
import android.os.Environment;
import android.os.Trace;
import android.os.UserHandle;
-import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
@@ -60,7 +60,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Map;
+import java.util.Set;
class InstallingSession {
final OriginInfo mOriginInfo;
@@ -599,7 +599,7 @@ class InstallingSession {
*/
private class MultiPackageInstallingSession {
private final List<InstallingSession> mChildInstallingSessions;
- private final Map<InstallRequest, Integer> mCurrentState;
+ private final Set<InstallRequest> mCurrentInstallRequests;
@NonNull
final PackageManagerService mPm;
final UserHandle mUser;
@@ -618,7 +618,7 @@ class InstallingSession {
final InstallingSession childInstallingSession = childInstallingSessions.get(i);
childInstallingSession.mParentInstallingSession = this;
}
- this.mCurrentState = new ArrayMap<>(mChildInstallingSessions.size());
+ mCurrentInstallRequests = new ArraySet<>(mChildInstallingSessions.size());
}
public void start() {
@@ -636,23 +636,24 @@ class InstallingSession {
}
public void tryProcessInstallRequest(InstallRequest request) {
- mCurrentState.put(request, request.getReturnCode());
- if (mCurrentState.size() != mChildInstallingSessions.size()) {
+ mCurrentInstallRequests.add(request);
+ if (mCurrentInstallRequests.size() != mChildInstallingSessions.size()) {
return;
}
int completeStatus = PackageManager.INSTALL_SUCCEEDED;
- for (Integer status : mCurrentState.values()) {
- if (status == PackageManager.INSTALL_UNKNOWN) {
+ for (InstallRequest installRequest : mCurrentInstallRequests) {
+ if (installRequest.getReturnCode() == PackageManager.INSTALL_UNKNOWN) {
return;
- } else if (status != PackageManager.INSTALL_SUCCEEDED) {
- completeStatus = status;
+ } else if (installRequest.getReturnCode() != PackageManager.INSTALL_SUCCEEDED) {
+ completeStatus = installRequest.getReturnCode();
break;
}
}
- final List<InstallRequest> installRequests = new ArrayList<>(mCurrentState.size());
- for (Map.Entry<InstallRequest, Integer> entry : mCurrentState.entrySet()) {
- entry.getKey().setReturnCode(completeStatus);
- installRequests.add(entry.getKey());
+ final List<InstallRequest> installRequests = new ArrayList<>(
+ mCurrentInstallRequests.size());
+ for (InstallRequest installRequest : mCurrentInstallRequests) {
+ installRequest.setReturnCode(completeStatus);
+ installRequests.add(installRequest);
}
int finalCompleteStatus = completeStatus;
mPm.mHandler.post(() -> processInstallRequests(
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 8534fabb5576..84324f2524fc 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -3,8 +3,6 @@ hackbod@google.com
jsharkey@android.com
jsharkey@google.com
narayan@google.com
-svetoslavganov@android.com
-svetoslavganov@google.com
include /PACKAGE_MANAGER_OWNERS
# apex support
@@ -26,16 +24,10 @@ per-file PackageManagerServiceCompilerMapping.java = file:dex/OWNERS
per-file PackageUsage.java = file:dex/OWNERS
# multi user / cross profile
-per-file CrossProfileAppsServiceImpl.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileAppsService.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileIntentFilter.java = omakoto@google.com, yamasani@google.com
-per-file CrossProfileIntentResolver.java = omakoto@google.com, yamasani@google.com
+per-file CrossProfile* = file:MULTIUSER_AND_ENTERPRISE_OWNERS
per-file RestrictionsSet.java = file:MULTIUSER_AND_ENTERPRISE_OWNERS
-per-file UserManager* = file:/MULTIUSER_OWNERS
per-file UserRestriction* = file:MULTIUSER_AND_ENTERPRISE_OWNERS
-per-file UserSystemPackageInstaller* = file:/MULTIUSER_OWNERS
-per-file UserTypeDetails.java = file:/MULTIUSER_OWNERS
-per-file UserTypeFactory.java = file:/MULTIUSER_OWNERS
+per-file User* = file:/MULTIUSER_OWNERS
# security
per-file KeySetHandle.java = cbrubaker@google.com, nnk@google.com
diff --git a/services/core/java/com/android/server/pm/PrepareResult.java b/services/core/java/com/android/server/pm/PrepareResult.java
deleted file mode 100644
index e074f44afff5..000000000000
--- a/services/core/java/com/android/server/pm/PrepareResult.java
+++ /dev/null
@@ -1,54 +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.server.pm;
-
-import android.annotation.Nullable;
-
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.AndroidPackage;
-
-/**
- * The set of data needed to successfully install the prepared package. This includes data that
- * will be used to scan and reconcile the package.
- */
-final class PrepareResult {
- public final boolean mReplace;
- public final int mScanFlags;
- public final int mParseFlags;
- @Nullable /* The original Package if it is being replaced, otherwise {@code null} */
- public final AndroidPackage mExistingPackage;
- public final ParsedPackage mPackageToScan;
- public final boolean mClearCodeCache;
- public final boolean mSystem;
- public final PackageSetting mOriginalPs;
- public final PackageSetting mDisabledPs;
-
- PrepareResult(boolean replace, int scanFlags,
- int parseFlags, AndroidPackage existingPackage,
- ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
- PackageSetting originalPs, PackageSetting disabledPs) {
- mReplace = replace;
- mScanFlags = scanFlags;
- mParseFlags = parseFlags;
- mExistingPackage = existingPackage;
- mPackageToScan = packageToScan;
- mClearCodeCache = clearCodeCache;
- mSystem = system;
- mOriginalPs = originalPs;
- mDisabledPs = disabledPs;
- }
-}
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 165b450ce65b..ffce69e00d24 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -38,34 +38,44 @@ import com.android.server.utils.WatchedLongSparseArray;
import java.util.List;
import java.util.Map;
+/**
+ * Package scan results and related request details used to reconcile the potential addition of
+ * one or more packages to the system.
+ *
+ * Reconcile will take a set of package details that need to be committed to the system and make
+ * sure that they are valid in the context of the system and the other installing apps. Any
+ * invalid state or app will result in a failed reconciliation and thus whatever operation (such
+ * as install) led to the request.
+ */
final class ReconcilePackageUtils {
public static Map<String, ReconciledPackage> reconcilePackages(
- final ReconcileRequest request, SharedLibrariesImpl sharedLibraries,
+ List<InstallRequest> installRequests,
+ Map<String, AndroidPackage> allPackages,
+ Map<String, Settings.VersionInfo> versionInfos,
+ SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms, Settings settings)
throws ReconcileFailure {
- final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
-
- final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
+ final Map<String, ReconciledPackage> result = new ArrayMap<>(installRequests.size());
// make a copy of the existing set of packages so we can combine them with incoming packages
final ArrayMap<String, AndroidPackage> combinedPackages =
- new ArrayMap<>(request.mAllPackages.size() + scannedPackages.size());
+ new ArrayMap<>(allPackages.size() + installRequests.size());
- combinedPackages.putAll(request.mAllPackages);
+ combinedPackages.putAll(allPackages);
final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries =
new ArrayMap<>();
- for (String installPackageName : scannedPackages.keySet()) {
- final ScanResult scanResult = scannedPackages.get(installPackageName);
+ for (InstallRequest installRequest : installRequests) {
+ final String installPackageName = installRequest.getParsedPackage().getPackageName();
// add / replace existing with incoming packages
- combinedPackages.put(scanResult.mPkgSetting.getPackageName(),
- scanResult.mRequest.mParsedPackage);
+ combinedPackages.put(installRequest.getScannedPackageSetting().getPackageName(),
+ installRequest.getParsedPackage());
// in the first pass, we'll build up the set of incoming shared libraries
final List<SharedLibraryInfo> allowedSharedLibInfos =
- sharedLibraries.getAllowedSharedLibInfos(scanResult);
+ sharedLibraries.getAllowedSharedLibInfos(installRequest);
if (allowedSharedLibInfos != null) {
for (SharedLibraryInfo info : allowedSharedLibInfos) {
if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
@@ -76,24 +86,18 @@ final class ReconcilePackageUtils {
}
}
- // the following may be null if we're just reconciling on boot (and not during install)
- final InstallRequest installRequest = request.mInstallRequests.get(installPackageName);
- final PrepareResult prepareResult = request.mPreparedPackages.get(installPackageName);
- final boolean isInstall = installRequest != null;
- if (isInstall && prepareResult == null) {
- throw new ReconcileFailure("Reconcile arguments are not balanced for "
- + installPackageName + "!");
- }
+
final DeletePackageAction deletePackageAction;
// we only want to try to delete for non system apps
- if (isInstall && prepareResult.mReplace && !prepareResult.mSystem) {
- final boolean killApp = (scanResult.mRequest.mScanFlags & SCAN_DONT_KILL_APP) == 0;
+ if (installRequest.isReplace() && !installRequest.isSystem()) {
+ final boolean killApp = (installRequest.getScanFlags() & SCAN_DONT_KILL_APP) == 0;
final int deleteFlags = PackageManager.DELETE_KEEP_DATA
| (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
deletePackageAction = DeletePackageHelper.mayDeletePackageLocked(
installRequest.getRemovedInfo(),
- prepareResult.mOriginalPs, prepareResult.mDisabledPs,
+ installRequest.getOriginalPackageSetting(),
+ installRequest.getDisabledPackageSetting(),
deleteFlags, null /* all users */);
if (deletePackageAction == null) {
throw new ReconcileFailure(
@@ -104,21 +108,24 @@ final class ReconcilePackageUtils {
deletePackageAction = null;
}
- final int scanFlags = scanResult.mRequest.mScanFlags;
- final int parseFlags = scanResult.mRequest.mParseFlags;
- final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
-
- final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
+ final int scanFlags = installRequest.getScanFlags();
+ final int parseFlags = installRequest.getParseFlags();
+ final ParsedPackage parsedPackage = installRequest.getParsedPackage();
+ final PackageSetting disabledPkgSetting = installRequest.getDisabledPackageSetting();
final PackageSetting lastStaticSharedLibSetting =
- scanResult.mStaticSharedLibraryInfo == null ? null
- : sharedLibraries.getStaticSharedLibLatestVersionSetting(scanResult);
+ installRequest.getStaticSharedLibraryInfo() == null ? null
+ : sharedLibraries.getStaticSharedLibLatestVersionSetting(
+ installRequest);
final PackageSetting signatureCheckPs =
- (prepareResult != null && lastStaticSharedLibSetting != null)
+ lastStaticSharedLibSetting != null
? lastStaticSharedLibSetting
- : scanResult.mPkgSetting;
+ : installRequest.getScannedPackageSetting();
boolean removeAppKeySetData = false;
boolean sharedUserSignaturesChanged = false;
SigningDetails signingDetails = null;
+ if (parsedPackage != null) {
+ signingDetails = parsedPackage.getSigningDetails();
+ }
SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr(
signatureCheckPs);
if (ksms.shouldCheckUpgradeKeySetLocked(
@@ -138,28 +145,21 @@ final class ReconcilePackageUtils {
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
}
}
- signingDetails = parsedPackage.getSigningDetails();
} else {
-
try {
- final Settings.VersionInfo versionInfo =
- request.mVersionInfos.get(installPackageName);
+ final Settings.VersionInfo versionInfo = versionInfos.get(installPackageName);
final boolean compareCompat = isCompatSignatureUpdateNeeded(versionInfo);
final boolean compareRecover = isRecoverSignatureUpdateNeeded(versionInfo);
- final boolean isRollback = installRequest != null
- && installRequest.isRollback();
+ final boolean isRollback = installRequest.isRollback();
final boolean compatMatch =
PackageManagerServiceUtils.verifySignatures(signatureCheckPs,
sharedUserSetting, disabledPkgSetting,
- parsedPackage.getSigningDetails(), compareCompat,
+ signingDetails, compareCompat,
compareRecover, isRollback);
// The new KeySets will be re-added later in the scanning process.
if (compatMatch) {
removeAppKeySetData = true;
}
- // We just determined the app is signed correctly, so bring
- // over the latest parsed certs.
- signingDetails = parsedPackage.getSigningDetails();
// if this is is a sharedUser, check to see if the new package is signed by a
// newer
@@ -257,13 +257,12 @@ final class ReconcilePackageUtils {
}
result.put(installPackageName,
- new ReconciledPackage(request, installRequest, scanResult.mPkgSetting,
- request.mPreparedPackages.get(installPackageName), scanResult,
+ new ReconciledPackage(installRequests, allPackages, installRequest,
deletePackageAction, allowedSharedLibInfos, signingDetails,
sharedUserSignaturesChanged, removeAppKeySetData));
}
- for (String installPackageName : scannedPackages.keySet()) {
+ for (InstallRequest installRequest : installRequests) {
// Check all shared libraries and map to their actual file path.
// We only do this here for apps not on a system dir, because those
// are the only ones that can fail an install due to this. We
@@ -271,16 +270,16 @@ final class ReconcilePackageUtils {
// library paths after the scan is done. Also during the initial
// scan don't update any libs as we do this wholesale after all
// apps are scanned to avoid dependency based scanning.
- final ScanResult scanResult = scannedPackages.get(installPackageName);
- if ((scanResult.mRequest.mScanFlags & SCAN_BOOTING) != 0
- || (scanResult.mRequest.mParseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
+ if ((installRequest.getScanFlags() & SCAN_BOOTING) != 0
+ || (installRequest.getParseFlags() & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR)
!= 0) {
continue;
}
+ final String installPackageName = installRequest.getParsedPackage().getPackageName();
try {
result.get(installPackageName).mCollectedSharedLibraryInfos =
sharedLibraries.collectSharedLibraryInfos(
- scanResult.mRequest.mParsedPackage, combinedPackages,
+ installRequest.getParsedPackage(), combinedPackages,
incomingSharedLibraries);
} catch (PackageManagerException e) {
throw new ReconcileFailure(e.error, e.getMessage());
diff --git a/services/core/java/com/android/server/pm/ReconcileRequest.java b/services/core/java/com/android/server/pm/ReconcileRequest.java
deleted file mode 100644
index 3568c153de91..000000000000
--- a/services/core/java/com/android/server/pm/ReconcileRequest.java
+++ /dev/null
@@ -1,59 +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.server.pm;
-
-import com.android.server.pm.pkg.AndroidPackage;
-
-import java.util.Collections;
-import java.util.Map;
-
-/**
- * Package scan results and related request details used to reconcile the potential addition of
- * one or more packages to the system.
- *
- * Reconcile will take a set of package details that need to be committed to the system and make
- * sure that they are valid in the context of the system and the other installing apps. Any
- * invalid state or app will result in a failed reconciliation and thus whatever operation (such
- * as install) led to the request.
- */
-final class ReconcileRequest {
- public final Map<String, ScanResult> mScannedPackages;
-
- public final Map<String, AndroidPackage> mAllPackages;
- public final Map<String, InstallRequest> mInstallRequests;
- public final Map<String, PrepareResult> mPreparedPackages;
- public final Map<String, Settings.VersionInfo> mVersionInfos;
-
- ReconcileRequest(Map<String, ScanResult> scannedPackages,
- Map<String, InstallRequest> installRequests,
- Map<String, PrepareResult> preparedPackages,
- Map<String, AndroidPackage> allPackages,
- Map<String, Settings.VersionInfo> versionInfos) {
- mScannedPackages = scannedPackages;
- mInstallRequests = installRequests;
- mPreparedPackages = preparedPackages;
- mAllPackages = allPackages;
- mVersionInfos = versionInfos;
- }
-
- ReconcileRequest(Map<String, ScanResult> scannedPackages,
- Map<String, AndroidPackage> allPackages,
- Map<String, Settings.VersionInfo> versionInfos) {
- this(scannedPackages, Collections.emptyMap(),
- Collections.emptyMap(), allPackages, versionInfos);
- }
-}
diff --git a/services/core/java/com/android/server/pm/ReconciledPackage.java b/services/core/java/com/android/server/pm/ReconciledPackage.java
index d4da6c798b15..701baee085ed 100644
--- a/services/core/java/com/android/server/pm/ReconciledPackage.java
+++ b/services/core/java/com/android/server/pm/ReconciledPackage.java
@@ -17,7 +17,6 @@
package com.android.server.pm;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.util.ArrayMap;
@@ -33,12 +32,9 @@ import java.util.Map;
* TODO: move most of the data contained here into a PackageSetting for commit.
*/
final class ReconciledPackage {
- public final ReconcileRequest mRequest;
- public final PackageSetting mPkgSetting;
- public final ScanResult mScanResult;
- // TODO: Remove install-specific details from the reconcile result
- @Nullable public final PrepareResult mPrepareResult;
- @Nullable public final InstallRequest mInstallRequest;
+ private final List<InstallRequest> mInstallRequests;
+ private final Map<String, AndroidPackage> mAllPackages;
+ @NonNull public final InstallRequest mInstallRequest;
public final DeletePackageAction mDeletePackageAction;
public final List<SharedLibraryInfo> mAllowedSharedLibraryInfos;
public final SigningDetails mSigningDetails;
@@ -46,21 +42,17 @@ final class ReconciledPackage {
public ArrayList<SharedLibraryInfo> mCollectedSharedLibraryInfos;
public final boolean mRemoveAppKeySetData;
- ReconciledPackage(ReconcileRequest request,
+ ReconciledPackage(List<InstallRequest> installRequests,
+ Map<String, AndroidPackage> allPackages,
InstallRequest installRequest,
- PackageSetting pkgSetting,
- PrepareResult prepareResult,
- ScanResult scanResult,
DeletePackageAction deletePackageAction,
List<SharedLibraryInfo> allowedSharedLibraryInfos,
SigningDetails signingDetails,
boolean sharedUserSignaturesChanged,
boolean removeAppKeySetData) {
- mRequest = request;
+ mInstallRequests = installRequests;
+ mAllPackages = allPackages;
mInstallRequest = installRequest;
- mPkgSetting = pkgSetting;
- mPrepareResult = prepareResult;
- mScanResult = scanResult;
mDeletePackageAction = deletePackageAction;
mAllowedSharedLibraryInfos = allowedSharedLibraryInfos;
mSigningDetails = signingDetails;
@@ -75,13 +67,13 @@ final class ReconciledPackage {
*/
@NonNull Map<String, AndroidPackage> getCombinedAvailablePackages() {
final ArrayMap<String, AndroidPackage> combined =
- new ArrayMap<>(mRequest.mAllPackages.size() + mRequest.mScannedPackages.size());
+ new ArrayMap<>(mAllPackages.size() + mInstallRequests.size());
- combined.putAll(mRequest.mAllPackages);
+ combined.putAll(mAllPackages);
- for (ScanResult scanResult : mRequest.mScannedPackages.values()) {
- combined.put(scanResult.mPkgSetting.getPackageName(),
- scanResult.mRequest.mParsedPackage);
+ for (InstallRequest installRequest : mInstallRequests) {
+ combined.put(installRequest.getScannedPackageSetting().getPackageName(),
+ installRequest.getParsedPackage());
}
return combined;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index bd3c7dd0a4b4..a905df98e577 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -462,7 +462,7 @@ final class ScanPackageUtils {
}
}
- return new ScanResult(request, true, pkgSetting, changedAbiCodePath,
+ return new ScanResult(request, pkgSetting, changedAbiCodePath,
!createNewPackage /* existingSettingCopied */,
Process.INVALID_UID /* previousAppId */ , sdkLibraryInfo,
staticSharedLibraryInfo, dynamicSharedLibraryInfos);
diff --git a/services/core/java/com/android/server/pm/ScanResult.java b/services/core/java/com/android/server/pm/ScanResult.java
index e2860ca327e7..750e893ebefb 100644
--- a/services/core/java/com/android/server/pm/ScanResult.java
+++ b/services/core/java/com/android/server/pm/ScanResult.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.SharedLibraryInfo;
import android.os.Process;
@@ -28,9 +29,7 @@ import java.util.List;
@VisibleForTesting
final class ScanResult {
/** The request that initiated the scan that produced this result. */
- public final ScanRequest mRequest;
- /** Whether or not the package scan was successful */
- public final boolean mSuccess;
+ @NonNull public final ScanRequest mRequest;
/**
* Whether or not the original PackageSetting needs to be updated with this result on
* commit.
@@ -58,7 +57,7 @@ final class ScanResult {
public final List<SharedLibraryInfo> mDynamicSharedLibraryInfos;
ScanResult(
- ScanRequest request, boolean success,
+ @NonNull ScanRequest request,
@Nullable PackageSetting pkgSetting,
@Nullable List<String> changedAbiCodePath, boolean existingSettingCopied,
int previousAppId,
@@ -66,7 +65,6 @@ final class ScanResult {
SharedLibraryInfo staticSharedLibraryInfo,
List<SharedLibraryInfo> dynamicSharedLibraryInfos) {
mRequest = request;
- mSuccess = success;
mPkgSetting = pkgSetting;
mChangedAbiCodePath = changedAbiCodePath;
mExistingSettingCopied = existingSettingCopied;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0eefbfed1fbd..12c9d4b37eed 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4185,15 +4185,16 @@ public final class Settings implements Watchable, Snappable {
// such as APEX
continue;
}
- // Need to create a data directory for all apps installed for this user.
- // Accumulate all required args and call the installer after mPackages lock
- // has been released
+ // We need to create the DE data directory for all apps installed for this user.
+ // (CE storage is not ready yet; the CE data directories will be created later,
+ // when the user is "unlocked".) Accumulate all required args, and call the
+ // installer after the mPackages lock has been released.
final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty();
final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(
ps.getVolumeUuid(), ps.getPackageName(), userHandle,
- StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE,
- ps.getAppId(), seInfo, ps.getPkg().getTargetSdkVersion(), usesSdk);
+ StorageManager.FLAG_STORAGE_DE, ps.getAppId(), seInfo,
+ ps.getPkg().getTargetSdkVersion(), usesSdk);
batch.createAppData(args);
} else {
// Make sure the app is excluded from storage mapping for this user
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 094e748685e5..aa23d8d75c8c 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -422,15 +422,18 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
* Given a package scanned result of a static shared library, returns its package setting of
* the latest version
*
- * @param scanResult The scanned result of a static shared library package.
+ * @param installRequest The install result of a static shared library package.
* @return The package setting that represents the latest version of shared library info.
*/
@Nullable
- PackageSetting getStaticSharedLibLatestVersionSetting(@NonNull ScanResult scanResult) {
+ PackageSetting getStaticSharedLibLatestVersionSetting(@NonNull InstallRequest installRequest) {
+ if (installRequest.getParsedPackage() == null) {
+ return null;
+ }
PackageSetting sharedLibPackage = null;
synchronized (mPm.mLock) {
final SharedLibraryInfo latestSharedLibraVersionLPr =
- getLatestStaticSharedLibraVersionLPr(scanResult.mRequest.mParsedPackage);
+ getLatestStaticSharedLibraVersionLPr(installRequest.getParsedPackage());
if (latestSharedLibraVersionLPr != null) {
sharedLibPackage = mPm.mSettings.getPackageLPr(
latestSharedLibraVersionLPr.getPackageName());
@@ -823,34 +826,35 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
* Compare the newly scanned package with current system state to see which of its declared
* shared libraries should be allowed to be added to the system.
*/
- List<SharedLibraryInfo> getAllowedSharedLibInfos(ScanResult scanResult) {
+ List<SharedLibraryInfo> getAllowedSharedLibInfos(InstallRequest installRequest) {
// Let's used the parsed package as scanResult.pkgSetting may be null
- final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
- if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null
- && scanResult.mDynamicSharedLibraryInfos == null) {
+ final ParsedPackage parsedPackage = installRequest.getParsedPackage();
+ if (installRequest.getSdkSharedLibraryInfo() == null
+ && installRequest.getStaticSharedLibraryInfo() == null
+ && installRequest.getDynamicSharedLibraryInfos() == null) {
return null;
}
// Any app can add new SDKs and static shared libraries.
- if (scanResult.mSdkSharedLibraryInfo != null) {
- return Collections.singletonList(scanResult.mSdkSharedLibraryInfo);
+ if (installRequest.getSdkSharedLibraryInfo() != null) {
+ return Collections.singletonList(installRequest.getSdkSharedLibraryInfo());
}
- if (scanResult.mStaticSharedLibraryInfo != null) {
- return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
+ if (installRequest.getStaticSharedLibraryInfo() != null) {
+ return Collections.singletonList(installRequest.getStaticSharedLibraryInfo());
}
- final boolean hasDynamicLibraries = parsedPackage.isSystem()
- && scanResult.mDynamicSharedLibraryInfos != null;
+ final boolean hasDynamicLibraries = parsedPackage != null && parsedPackage.isSystem()
+ && installRequest.getDynamicSharedLibraryInfos() != null;
if (!hasDynamicLibraries) {
return null;
}
- final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
- .isUpdatedSystemApp();
+ final boolean isUpdatedSystemApp = installRequest.getScannedPackageSetting() != null
+ && installRequest.getScannedPackageSetting().getPkgState().isUpdatedSystemApp();
// We may not yet have disabled the updated package yet, so be sure to grab the
// current setting if that's the case.
final PackageSetting updatedSystemPs = isUpdatedSystemApp
- ? scanResult.mRequest.mDisabledPkgSetting == null
- ? scanResult.mRequest.mOldPkgSetting
- : scanResult.mRequest.mDisabledPkgSetting
+ ? installRequest.getDisabledPackageSetting() == null
+ ? installRequest.getScanRequestOldPackageSetting()
+ : installRequest.getDisabledPackageSetting()
: null;
if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
|| updatedSystemPs.getPkg().getLibraryNames() == null)) {
@@ -859,8 +863,8 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
return null;
}
final ArrayList<SharedLibraryInfo> infos =
- new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
- for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
+ new ArrayList<>(installRequest.getDynamicSharedLibraryInfos().size());
+ for (SharedLibraryInfo info : installRequest.getDynamicSharedLibraryInfos()) {
final String name = info.getName();
if (isUpdatedSystemApp) {
// New library entries can only be added through the
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 477e260a4818..aeb11b77f794 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -158,7 +158,7 @@ public final class StorageEventHelper extends StorageEventListener {
final AndroidPackage pkg;
try {
pkg = installPackageHelper.scanSystemPackageTracedLI(
- ps.getPath(), parseFlags, SCAN_INITIAL, null);
+ ps.getPath(), parseFlags, SCAN_INITIAL);
loaded.add(pkg);
} catch (PackageManagerException e) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4966f94d6df0..cf0ea437ea38 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -104,7 +104,6 @@ import android.util.StatsEvent;
import android.util.TimeUtils;
import android.util.TypedValue;
import android.util.Xml;
-import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -625,14 +624,7 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mUserStates")
private final WatchedUserStates mUserStates = new WatchedUserStates();
- /**
- * Set on on devices that support background users (key) running on secondary displays (value).
- */
- // TODO(b/244644281): move such logic to a different class (like UserDisplayAssigner)
- @Nullable
- @GuardedBy("mUsersOnSecondaryDisplays")
- private final SparseIntArray mUsersOnSecondaryDisplays;
- private final boolean mUsersOnSecondaryDisplaysEnabled;
+ private final UserVisibilityMediator mUserVisibilityMediator;
private static UserManagerService sInstance;
@@ -708,8 +700,7 @@ public class UserManagerService extends IUserManager.Stub {
@VisibleForTesting
UserManagerService(Context context) {
this(context, /* pm= */ null, /* userDataPreparer= */ null,
- /* packagesLock= */ new Object(), context.getCacheDir(), /* users= */ null,
- /* usersOnSecondaryDisplays= */ null);
+ /* packagesLock= */ new Object(), context.getCacheDir(), /* users= */ null);
}
/**
@@ -720,13 +711,13 @@ public class UserManagerService extends IUserManager.Stub {
UserManagerService(Context context, PackageManagerService pm, UserDataPreparer userDataPreparer,
Object packagesLock) {
this(context, pm, userDataPreparer, packagesLock, Environment.getDataDirectory(),
- /* users= */ null, /* usersOnSecondaryDisplays= */ null);
+ /* users= */ null);
}
@VisibleForTesting
UserManagerService(Context context, PackageManagerService pm,
UserDataPreparer userDataPreparer, Object packagesLock, File dataDir,
- SparseArray<UserData> users, @Nullable SparseIntArray usersOnSecondaryDisplays) {
+ SparseArray<UserData> users) {
mContext = context;
mPm = pm;
mPackagesLock = packagesLock;
@@ -756,14 +747,7 @@ public class UserManagerService extends IUserManager.Stub {
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
emulateSystemUserModeIfNeeded();
- mUsersOnSecondaryDisplaysEnabled = UserManager.isUsersOnSecondaryDisplaysEnabled();
- if (mUsersOnSecondaryDisplaysEnabled) {
- mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
- ? new SparseIntArray() // default behavior
- : usersOnSecondaryDisplays; // passed by unit test
- } else {
- mUsersOnSecondaryDisplays = null;
- }
+ mUserVisibilityMediator = new UserVisibilityMediator(this);
}
void systemReady() {
@@ -1652,7 +1636,8 @@ public class UserManagerService extends IUserManager.Stub {
return isProfileUnchecked(userId);
}
- private boolean isProfileUnchecked(@UserIdInt int userId) {
+ // TODO(b/244644281): make it private once UserVisibilityMediator don't use it anymore
+ boolean isProfileUnchecked(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isProfile();
@@ -1729,11 +1714,6 @@ public class UserManagerService extends IUserManager.Stub {
return userId == getCurrentUserId();
}
- @VisibleForTesting
- boolean isUsersOnSecondaryDisplaysEnabled() {
- return mUsersOnSecondaryDisplaysEnabled;
- }
-
@Override
public boolean isUserVisible(@UserIdInt int userId) {
int callingUserId = UserHandle.getCallingUserId();
@@ -1744,69 +1724,7 @@ public class UserManagerService extends IUserManager.Stub {
+ ") is visible");
}
- return isUserVisibleUnchecked(userId);
- }
-
- @VisibleForTesting
- boolean isUserVisibleUnchecked(@UserIdInt int userId) {
- // First check current foreground user and their profiles (on main display)
- if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- return true;
- }
-
- // Device doesn't support multiple users on multiple displays, so only users checked above
- // can be visible
- if (!mUsersOnSecondaryDisplaysEnabled) {
- return false;
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
- }
- }
-
- @VisibleForTesting
- int getDisplayAssignedToUser(@UserIdInt int userId) {
- if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- return Display.DEFAULT_DISPLAY;
- }
-
- if (!mUsersOnSecondaryDisplaysEnabled) {
- return Display.INVALID_DISPLAY;
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
- }
- }
-
- @VisibleForTesting
- int getUserAssignedToDisplay(int displayId) {
- if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
- return getCurrentUserId();
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
- continue;
- }
- int userId = mUsersOnSecondaryDisplays.keyAt(i);
- if (!isProfileUnchecked(userId)) {
- return userId;
- } else if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
- + "a profile", displayId, userId);
- }
- }
- }
-
- int currentUserId = getCurrentUserId();
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning "
- + "current user (%d) instead", displayId, currentUserId);
- }
- return currentUserId;
+ return mUserVisibilityMediator.isUserVisible(userId);
}
/**
@@ -1850,54 +1768,9 @@ public class UserManagerService extends IUserManager.Stub {
return false;
}
- // TODO(b/239982558): try to merge with isUserVisibleUnchecked() (once both are unit tested)
- /**
- * See {@link UserManagerInternal#isUserVisible(int, int)}.
- */
+ // Called by UserManagerServiceShellCommand
boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
- if (displayId == Display.INVALID_DISPLAY) {
- return false;
- }
- if (!mUsersOnSecondaryDisplaysEnabled) {
- return isCurrentUserOrRunningProfileOfCurrentUser(userId);
- }
-
- // TODO(b/244644281): temporary workaround to let WM use this API without breaking current
- // behavior - return true for current user / profile for any display (other than those
- // explicitly assigned to another users), otherwise they wouldn't be able to launch
- // activities on other non-passenger displays, like cluster, display, or virtual displays).
- // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
- // would be updated by DisplayManagerService when displays are created / initialized.
- if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- synchronized (mUsersOnSecondaryDisplays) {
- boolean assignedToUser = false;
- boolean assignedToAnotherUser = false;
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
- if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
- assignedToUser = true;
- break;
- } else {
- assignedToAnotherUser = true;
- // Cannot break because it could be assigned to a profile of the user
- // (and we better not assume that the iteration will check for the
- // parent user before its profiles)
- }
- }
- }
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
- + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
- userId, displayId, assignedToUser, assignedToAnotherUser,
- mUsersOnSecondaryDisplays);
- }
- return assignedToUser || !assignedToAnotherUser;
- }
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY) == displayId;
- }
+ return mUserVisibilityMediator.isUserVisible(userId, displayId);
}
@Override
@@ -1915,7 +1788,7 @@ public class UserManagerService extends IUserManager.Stub {
for (int i = 0; i < usersSize; i++) {
UserInfo ui = mUsers.valueAt(i).info;
if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
- && isUserVisibleUnchecked(ui.id)) {
+ && mUserVisibilityMediator.isUserVisible(ui.id)) {
visibleUsers.add(UserHandle.of(ui.id));
}
}
@@ -4666,9 +4539,12 @@ public class UserManagerService extends IUserManager.Stub {
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
t.traceEnd();
+ // Only prepare DE storage here. CE storage will be prepared later, when the user is
+ // unlocked. We do this to ensure that CE storage isn't prepared before the CE key is
+ // saved to disk. This also matches what is done for user 0.
t.traceBegin("prepareUserData");
mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
- StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
+ StorageManager.FLAG_STORAGE_DE);
t.traceEnd();
t.traceBegin("LSS.createNewUser");
@@ -6248,9 +6124,15 @@ public class UserManagerService extends IUserManager.Stub {
final long nowRealtime = SystemClock.elapsedRealtime();
final StringBuilder sb = new StringBuilder();
- if (args != null && args.length > 0 && args[0].equals("--user")) {
- dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
- return;
+ if (args != null && args.length > 0) {
+ switch (args[0]) {
+ case "--user":
+ dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
+ return;
+ case "--visibility-mediator":
+ mUserVisibilityMediator.dump(pw);
+ return;
+ }
}
final int currentUserId = getCurrentUserId();
@@ -6313,18 +6195,9 @@ public class UserManagerService extends IUserManager.Stub {
}
} // synchronized (mPackagesLock)
- // Multiple Users on Multiple Display info
- pw.println(" Supports users on secondary displays: " + mUsersOnSecondaryDisplaysEnabled);
- // mUsersOnSecondaryDisplaysEnabled is set on constructor, while the UserManager API is
- // set dynamically, so print both to help cases where the developer changed it on the fly
- pw.println(" UM.isUsersOnSecondaryDisplaysEnabled(): "
- + UserManager.isUsersOnSecondaryDisplaysEnabled());
- if (mUsersOnSecondaryDisplaysEnabled) {
- pw.print(" Users on secondary displays: ");
- synchronized (mUsersOnSecondaryDisplays) {
- pw.println(mUsersOnSecondaryDisplays);
- }
- }
+ pw.println();
+ mUserVisibilityMediator.dump(pw);
+ pw.println();
// Dump some capabilities
pw.println();
@@ -6896,133 +6769,33 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
- public void assignUserToDisplay(int userId, int displayId) {
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
- }
-
- // NOTE: Using Boolean instead of boolean as it will be re-used below
- Boolean isProfile = null;
- if (displayId == Display.DEFAULT_DISPLAY) {
- if (mUsersOnSecondaryDisplaysEnabled) {
- // Profiles are only supported in the default display, but it cannot return yet
- // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
- // (this is done indirectly below when it checks that the profile parent is the
- // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
- isProfile = isProfileUnchecked(userId);
- }
- if (isProfile == null || !isProfile) {
- // Don't need to do anything because methods (such as isUserVisible()) already
- // know that the current user (and their profiles) is assigned to the default
- // display.
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "ignoring on default display");
- }
- return;
- }
- }
-
- if (!mUsersOnSecondaryDisplaysEnabled) {
- throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", "
- + displayId + ") called on device that doesn't support multiple "
- + "users on multiple displays");
- }
-
- Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system "
- + "user to secondary display (%d)", displayId);
- Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY,
- "Cannot assign to INVALID_DISPLAY (%d)", displayId);
-
- int currentUserId = getCurrentUserId();
- Preconditions.checkArgument(userId != currentUserId,
- "Cannot assign current user (%d) to other displays", currentUserId);
-
- if (isProfile == null) {
- isProfile = isProfileUnchecked(userId);
- }
- synchronized (mUsersOnSecondaryDisplays) {
- if (isProfile) {
- // Profile can only start in the same display as parent. And for simplicity,
- // that display must be the DEFAULT_DISPLAY.
- Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
- "Profile user can only be started in the default display");
- int parentUserId = getProfileParentId(userId);
- Preconditions.checkArgument(parentUserId == currentUserId,
- "Only profile of current user can be assigned to a display");
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "Ignoring profile user %d on default display", userId);
- }
- return;
- }
-
- // Check if display is available
- for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
- int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
- int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
- i, assignedUserId, assignedDisplayId);
- }
- if (displayId == assignedDisplayId) {
- throw new IllegalStateException("Cannot assign user " + userId + " to "
- + "display " + displayId + " because such display is already "
- + "assigned to user " + assignedUserId);
- }
- if (userId == assignedUserId) {
- throw new IllegalStateException("Cannot assign user " + userId + " to "
- + "display " + displayId + " because such user is as already "
- + "assigned to display " + assignedDisplayId);
- }
- }
-
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "Adding full user %d -> display %d", userId, displayId);
- }
- mUsersOnSecondaryDisplays.put(userId, displayId);
- }
+ public void assignUserToDisplay(@UserIdInt int userId, int displayId) {
+ mUserVisibilityMediator.assignUserToDisplay(userId, displayId);
}
@Override
public void unassignUserFromDisplay(@UserIdInt int userId) {
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "unassignUserFromDisplay(%d)", userId);
- }
- if (!mUsersOnSecondaryDisplaysEnabled) {
- // Don't need to do anything because methods (such as isUserVisible()) already know
- // that the current user (and their profiles) is assigned to the default display.
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "ignoring when device doesn't support MUMD");
- }
- return;
- }
-
- synchronized (mUsersOnSecondaryDisplays) {
- if (DBG_MUMD) {
- Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
- mUsersOnSecondaryDisplays);
- }
- mUsersOnSecondaryDisplays.delete(userId);
- }
+ mUserVisibilityMediator.unassignUserFromDisplay(userId);
}
@Override
- public boolean isUserVisible(int userId) {
- return isUserVisibleUnchecked(userId);
+ public boolean isUserVisible(@UserIdInt int userId) {
+ return mUserVisibilityMediator.isUserVisible(userId);
}
@Override
- public boolean isUserVisible(int userId, int displayId) {
- return isUserVisibleOnDisplay(userId, displayId);
+ public boolean isUserVisible(@UserIdInt int userId, int displayId) {
+ return mUserVisibilityMediator.isUserVisible(userId, displayId);
}
@Override
- public int getDisplayAssignedToUser(int userId) {
- return UserManagerService.this.getDisplayAssignedToUser(userId);
+ public int getDisplayAssignedToUser(@UserIdInt int userId) {
+ return mUserVisibilityMediator.getDisplayAssignedToUser(userId);
}
@Override
- public int getUserAssignedToDisplay(int displayId) {
- return UserManagerService.this.getUserAssignedToDisplay(displayId);
+ public @UserIdInt int getUserAssignedToDisplay(int displayId) {
+ return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
}
} // class LocalService
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
new file mode 100644
index 000000000000..f725c486ec1f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -0,0 +1,350 @@
+/*
+ * 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 android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.IndentingPrintWriter;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.utils.Slogf;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for deciding whether a user is visible (or visible for a given display).
+ *
+ * <p>This class is thread safe.
+ */
+// TODO(b/244644281): improve javadoc (for example, explain all cases / modes)
+public final class UserVisibilityMediator {
+
+ private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+
+ private static final String TAG = UserVisibilityMediator.class.getSimpleName();
+
+ private final Object mLock = new Object();
+
+ // TODO(b/244644281): should not depend on service, but keep its own internal state (like
+ // current user and profile groups), but it is initially as the code was just moved from UMS
+ // "as is". Similarly, it shouldn't need to pass the SparseIntArray on constructor (which was
+ // added to UMS for testing purposes)
+ private final UserManagerService mService;
+
+ private final boolean mUsersOnSecondaryDisplaysEnabled;
+
+ @Nullable
+ @GuardedBy("mLock")
+ private final SparseIntArray mUsersOnSecondaryDisplays;
+
+ UserVisibilityMediator(UserManagerService service) {
+ this(service, UserManager.isUsersOnSecondaryDisplaysEnabled(),
+ /* usersOnSecondaryDisplays= */ null);
+ }
+
+ @VisibleForTesting
+ UserVisibilityMediator(UserManagerService service, boolean usersOnSecondaryDisplaysEnabled,
+ @Nullable SparseIntArray usersOnSecondaryDisplays) {
+ mService = service;
+ mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
+ ? new SparseIntArray() // default behavior
+ : usersOnSecondaryDisplays; // passed by unit test
+ } else {
+ mUsersOnSecondaryDisplays = null;
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
+ */
+ public void assignUserToDisplay(int userId, int displayId) {
+ if (DBG) {
+ Slogf.d(TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
+ }
+
+ // NOTE: Using Boolean instead of boolean as it will be re-used below
+ Boolean isProfile = null;
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ // Profiles are only supported in the default display, but it cannot return yet
+ // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
+ // (this is done indirectly below when it checks that the profile parent is the
+ // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
+ isProfile = isProfileUnchecked(userId);
+ }
+ if (isProfile == null || !isProfile) {
+ // Don't need to do anything because methods (such as isUserVisible()) already
+ // know that the current user (and their profiles) is assigned to the default
+ // display.
+ if (DBG) {
+ Slogf.d(TAG, "ignoring on default display");
+ }
+ return;
+ }
+ }
+
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", "
+ + displayId + ") called on device that doesn't support multiple "
+ + "users on multiple displays");
+ }
+
+ Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system "
+ + "user to secondary display (%d)", displayId);
+ Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY,
+ "Cannot assign to INVALID_DISPLAY (%d)", displayId);
+
+ int currentUserId = getCurrentUserId();
+ Preconditions.checkArgument(userId != currentUserId,
+ "Cannot assign current user (%d) to other displays", currentUserId);
+
+ if (isProfile == null) {
+ isProfile = isProfileUnchecked(userId);
+ }
+ synchronized (mLock) {
+ if (isProfile) {
+ // Profile can only start in the same display as parent. And for simplicity,
+ // that display must be the DEFAULT_DISPLAY.
+ Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
+ "Profile user can only be started in the default display");
+ int parentUserId = getProfileParentId(userId);
+ Preconditions.checkArgument(parentUserId == currentUserId,
+ "Only profile of current user can be assigned to a display");
+ if (DBG) {
+ Slogf.d(TAG, "Ignoring profile user %d on default display", userId);
+ }
+ return;
+ }
+
+ // Check if display is available
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
+ int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
+ if (DBG) {
+ Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
+ i, assignedUserId, assignedDisplayId);
+ }
+ if (displayId == assignedDisplayId) {
+ throw new IllegalStateException("Cannot assign user " + userId + " to "
+ + "display " + displayId + " because such display is already "
+ + "assigned to user " + assignedUserId);
+ }
+ if (userId == assignedUserId) {
+ throw new IllegalStateException("Cannot assign user " + userId + " to "
+ + "display " + displayId + " because such user is as already "
+ + "assigned to display " + assignedDisplayId);
+ }
+ }
+
+ if (DBG) {
+ Slogf.d(TAG, "Adding full user %d -> display %d", userId, displayId);
+ }
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+ */
+ public void unassignUserFromDisplay(int userId) {
+ if (DBG) {
+ Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+ }
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ // Don't need to do anything because methods (such as isUserVisible()) already know
+ // that the current user (and their profiles) is assigned to the default display.
+ if (DBG) {
+ Slogf.d(TAG, "ignoring when device doesn't support MUMD");
+ }
+ return;
+ }
+
+ synchronized (mLock) {
+ if (DBG) {
+ Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
+ mUsersOnSecondaryDisplays);
+ }
+ mUsersOnSecondaryDisplays.delete(userId);
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#isUserVisible(int)}.
+ */
+ public boolean isUserVisible(int userId) {
+ // First check current foreground user and their profiles (on main display)
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ return true;
+ }
+
+ // Device doesn't support multiple users on multiple displays, so only users checked above
+ // can be visible
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ return false;
+ }
+
+ synchronized (mLock) {
+ return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#isUserVisible(int, int)}.
+ */
+ public boolean isUserVisible(int userId, int displayId) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ return false;
+ }
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ return isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ }
+
+ // TODO(b/244644281): temporary workaround to let WM use this API without breaking current
+ // behavior - return true for current user / profile for any display (other than those
+ // explicitly assigned to another users), otherwise they wouldn't be able to launch
+ // activities on other non-passenger displays, like cluster, display, or virtual displays).
+ // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
+ // would be updated by DisplayManagerService when displays are created / initialized.
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ synchronized (mLock) {
+ boolean assignedToUser = false;
+ boolean assignedToAnotherUser = false;
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
+ if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
+ assignedToUser = true;
+ break;
+ } else {
+ assignedToAnotherUser = true;
+ // Cannot break because it could be assigned to a profile of the user
+ // (and we better not assume that the iteration will check for the
+ // parent user before its profiles)
+ }
+ }
+ }
+ if (DBG) {
+ Slogf.d(TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
+ + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
+ userId, displayId, assignedToUser, assignedToAnotherUser,
+ mUsersOnSecondaryDisplays);
+ }
+ return assignedToUser || !assignedToAnotherUser;
+ }
+ }
+
+ synchronized (mLock) {
+ return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY) == displayId;
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
+ */
+ public int getDisplayAssignedToUser(int userId) {
+ if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+ return Display.DEFAULT_DISPLAY;
+ }
+
+ if (!mUsersOnSecondaryDisplaysEnabled) {
+ return Display.INVALID_DISPLAY;
+ }
+
+ synchronized (mLock) {
+ return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
+ }
+ }
+
+ /**
+ * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
+ */
+ public int getUserAssignedToDisplay(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
+ return getCurrentUserId();
+ }
+
+ synchronized (mLock) {
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
+ continue;
+ }
+ int userId = mUsersOnSecondaryDisplays.keyAt(i);
+ if (!isProfileUnchecked(userId)) {
+ return userId;
+ } else if (DBG) {
+ Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
+ + "a profile", displayId, userId);
+ }
+ }
+ }
+
+ int currentUserId = getCurrentUserId();
+ if (DBG) {
+ Slogf.d(TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning "
+ + "current user (%d) instead", displayId, currentUserId);
+ }
+ return currentUserId;
+ }
+
+ private void dump(IndentingPrintWriter ipw) {
+ ipw.println("UserVisibilityManager");
+ ipw.increaseIndent();
+
+ ipw.print("Supports users on secondary displays: ");
+ ipw.println(mUsersOnSecondaryDisplaysEnabled);
+
+ if (mUsersOnSecondaryDisplaysEnabled) {
+ ipw.print("Users on secondary displays: ");
+ synchronized (mLock) {
+ ipw.println(mUsersOnSecondaryDisplays);
+ }
+ }
+
+ ipw.decreaseIndent();
+ }
+
+ void dump(PrintWriter pw) {
+ if (pw instanceof IndentingPrintWriter) {
+ dump((IndentingPrintWriter) pw);
+ return;
+ }
+ dump(new IndentingPrintWriter(pw));
+ }
+
+ // TODO(b/244644281): remove methods below once this class caches that state
+ private @UserIdInt int getCurrentUserId() {
+ return mService.getCurrentUserId();
+ }
+
+ private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
+ return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId);
+ }
+
+ private boolean isProfileUnchecked(@UserIdInt int userId) {
+ return mService.isProfileUnchecked(userId);
+ }
+
+ private @UserIdInt int getProfileParentId(@UserIdInt int userId) {
+ return mService.getProfileParentId(userId);
+ }
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index a6fac4d60fe7..98b5c1ba639b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -256,6 +256,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final int SHORT_PRESS_POWER_GO_HOME = 4;
static final int SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME = 5;
static final int SHORT_PRESS_POWER_LOCK_OR_SLEEP = 6;
+ static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
// must match: config_LongPressOnPowerBehavior in config.xml
static final int LONG_PRESS_POWER_NOTHING = 0;
@@ -969,7 +970,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
- } else if (count == 1 && interactive && !beganFromNonInteractive) {
+ } else if (count == 1 && interactive) {
+ if (beganFromNonInteractive) {
+ // The "screen is off" case, where we might want to start dreaming on power button
+ // press.
+ attemptToDreamFromShortPowerButtonPress(false, () -> {});
+ return;
+ }
+
if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
@@ -1018,11 +1026,39 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
break;
}
+ case SHORT_PRESS_POWER_DREAM_OR_SLEEP: {
+ attemptToDreamFromShortPowerButtonPress(
+ true,
+ () -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
+ break;
+ }
}
}
}
/**
+ * Attempt to dream from a power button press.
+ *
+ * @param isScreenOn Whether the screen is currently on.
+ * @param noDreamAction The action to perform if dreaming is not possible.
+ */
+ private void attemptToDreamFromShortPowerButtonPress(
+ boolean isScreenOn, Runnable noDreamAction) {
+ if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP) {
+ noDreamAction.run();
+ return;
+ }
+
+ final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+ if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
+ noDreamAction.run();
+ return;
+ }
+
+ dreamManagerInternal.requestDream();
+ }
+
+ /**
* Sends the default display to sleep as a result of a power button press.
*
* @return {@code true} if the device was sent to sleep, {@code false} if the device did not
@@ -1593,7 +1629,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// If there's a dream running then use home to escape the dream
// but don't actually go home.
- if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
+ final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+ if (dreamManagerInternal != null && dreamManagerInternal.isDreaming()) {
mDreamManagerInternal.stopDream(false /*immediate*/, "short press on home" /*reason*/);
return;
}
@@ -2529,6 +2566,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
+ private DreamManagerInternal getDreamManagerInternal() {
+ if (mDreamManagerInternal == null) {
+ // If mDreamManagerInternal is null, attempt to re-fetch it.
+ mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
+ }
+
+ return mDreamManagerInternal;
+ }
+
private void updateWakeGestureListenerLp() {
if (shouldEnableWakeGestureLp()) {
mWakeGestureListener.requestWakeUpTrigger();
@@ -4131,6 +4177,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case KeyEvent.KEYCODE_DEMO_APP_2:
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4: {
+ // TODO(b/254604589): Dispatch KeyEvent to System UI.
+ sendSystemKeyToStatusBarAsync(keyCode);
+
// Just drop if keys are not intercepted for direct key.
result &= ~ACTION_PASS_TO_USER;
break;
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 004312f06119..11766a3d70bd 100644
--- a/services/core/java/com/android/server/utils/EventLogger.java
+++ b/services/core/java/com/android/server/utils/EventLogger.java
@@ -43,7 +43,7 @@ public class EventLogger {
/**
* The maximum number of events to keep in {@code mEvents}.
*
- * <p>Calling {@link #log} when the size of {@link #mEvents} matches the threshold will
+ * <p>Calling {@link #enqueue} when the size of {@link #mEvents} matches the threshold will
* cause the oldest event to be evicted.
*/
private final int mMemSize;
@@ -60,7 +60,7 @@ public class EventLogger {
}
/** Enqueues {@code event} to be logged. */
- public synchronized void log(Event event) {
+ public synchronized void enqueue(Event event) {
if (mEvents.size() >= mMemSize) {
mEvents.removeLast();
}
@@ -69,24 +69,14 @@ public class EventLogger {
}
/**
- * Add a string-based event to the log, and print it to logcat as info.
- * @param msg the message for the logs
- * @param tag the logcat tag to use
- */
- public synchronized void loglogi(String msg, String tag) {
- final Event event = new StringEvent(msg);
- log(event.printLog(tag));
- }
-
- /**
- * Same as {@link #loglogi(String, String)} but specifying the logcat type
+ * Add a string-based event to the log, and print it to logcat with a specific severity.
* @param msg the message for the logs
* @param logType the type of logcat entry
* @param tag the logcat tag to use
*/
- public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
+ public synchronized void enqueueAndLog(String msg, @Event.LogType int logType, String tag) {
final Event event = new StringEvent(msg);
- log(event.printLog(logType, tag));
+ enqueue(event.printLog(logType, tag));
}
/** Dumps events using {@link PrintWriter}. */
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 7d84bdf78056..d7c5e9373ad3 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -424,7 +424,7 @@ class ActivityStartInterceptor {
try {
harmfulAppWarning = mService.getPackageManager()
.getHarmfulAppWarning(mAInfo.packageName, mUserId);
- } catch (RemoteException ex) {
+ } catch (RemoteException | IllegalArgumentException ex) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index d2c098b73e71..a487797ad1f3 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1448,6 +1448,12 @@ public class AppTransition implements Dump {
|| transit == TRANSIT_OLD_ACTIVITY_RELAUNCH;
}
+ static boolean isTaskFragmentTransitOld(@TransitionOldType int transit) {
+ return transit == TRANSIT_OLD_TASK_FRAGMENT_OPEN
+ || transit == TRANSIT_OLD_TASK_FRAGMENT_CLOSE
+ || transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
+ }
+
static boolean isChangeTransitOld(@TransitionOldType int transit) {
return transit == TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE
|| transit == TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 739f41f170aa..8b3444318636 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -47,7 +47,6 @@ import static android.view.Display.STATE_UNKNOWN;
import static android.view.Display.isSuspendedState;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
@@ -55,6 +54,7 @@ import static android.view.Surface.ROTATION_90;
import static android.view.View.GONE;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
@@ -216,7 +216,6 @@ import android.view.InputDevice;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.MagnificationSpec;
import android.view.PrivacyIndicatorBounds;
import android.view.RemoteAnimationDefinition;
@@ -227,6 +226,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -788,11 +788,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// higher window hierarchy, we don't give it focus if the next IME layering target
// doesn't request IME visible.
if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null
- || !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME))) {
+ || !mImeLayeringTarget.isRequestedVisible(ime()))) {
return false;
}
if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
- && !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME)
+ && !mImeLayeringTarget.isRequestedVisible(ime())
&& !mImeLayeringTarget.isVisibleRequested()) {
return false;
}
@@ -2059,7 +2059,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// is opened for logging metrics.
if (mWmService.mAccessibilityController.hasCallbacks()) {
final boolean isImeShow = mImeControlTarget != null
- && mImeControlTarget.getRequestedVisibility(ITYPE_IME);
+ && mImeControlTarget.isRequestedVisible(ime());
mWmService.mAccessibilityController.updateImeVisibilityIfNeeded(mDisplayId, isImeShow);
}
}
@@ -5662,7 +5662,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final int type = win.mAttrs.type;
final int privateFlags = win.mAttrs.privateFlags;
final boolean stickyHideNav =
- !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
+ !win.isRequestedVisible(navigationBars())
&& win.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
return (!stickyHideNav || ignoreRequest) && type != TYPE_INPUT_METHOD
&& type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME
@@ -6672,7 +6672,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
class RemoteInsetsControlTarget implements InsetsControlTarget {
private final IDisplayWindowInsetsController mRemoteInsetsController;
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
private final boolean mCanShowTransient;
RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) {
@@ -6685,12 +6685,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* Notifies the remote insets controller that the top focused window has changed.
*
* @param component The application component that is open in the top focussed window.
- * @param requestedVisibilities The insets visibilities requested by the focussed window.
+ * @param requestedVisibleTypes The insets types requested visible by the focused window.
*/
void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ @InsetsType int requestedVisibleTypes) {
try {
- mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibilities);
+ mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibleTypes);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver package in top focused window change", e);
}
@@ -6726,7 +6726,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ public void hideInsets(@InsetsType int types, boolean fromIme) {
try {
mRemoteInsetsController.hideInsets(types, fromIme);
} catch (RemoteException e) {
@@ -6740,15 +6740,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
@Override
- public boolean getRequestedVisibility(@InternalInsetsType int type) {
- if (type == ITYPE_IME) {
+ public boolean isRequestedVisible(@InsetsType int types) {
+ if (types == ime()) {
return getInsetsStateController().getImeSourceProvider().isImeShowing();
}
- return mRequestedVisibilities.getVisibility(type);
+ return (mRequestedVisibleTypes & types) != 0;
}
- void setRequestedVisibilities(InsetsVisibilities requestedVisibilities) {
- mRequestedVisibilities.set(requestedVisibilities);
+ @Override
+ public @InsetsType int getRequestedVisibleTypes() {
+ return mRequestedVisibleTypes;
+ }
+
+ /**
+ * @see #getRequestedVisibleTypes()
+ */
+ void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 442777a20281..87239944fa60 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -19,14 +19,11 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.TYPE_INTERNAL;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
+import static android.view.InsetsFrameProvider.SOURCE_FRAME;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -47,7 +44,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLO
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -127,6 +123,7 @@ import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.View;
import android.view.ViewDebug;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowLayout;
@@ -209,14 +206,11 @@ public class DisplayPolicy {
private StatusBarManagerInternal mStatusBarManagerInternal;
@Px
- private int mBottomGestureAdditionalInset;
- @Px
private int mLeftGestureInset;
@Px
private int mRightGestureInset;
private boolean mCanSystemBarsBeShownByUser;
- private boolean mNavButtonForcedVisible;
StatusBarManagerInternal getStatusBarManagerInternal() {
synchronized (mServiceAcquireLock) {
@@ -240,7 +234,6 @@ public class DisplayPolicy {
private volatile boolean mHasNavigationBar;
// Can the navigation bar ever move to the side?
private volatile boolean mNavigationBarCanMove;
- private volatile boolean mNavigationBarLetsThroughTaps;
private volatile boolean mNavigationBarAlwaysShowOnSideGesture;
// Written by vr manager thread, only read in this class.
@@ -324,6 +317,7 @@ public class DisplayPolicy {
private int mLastDisableFlags;
private int mLastAppearance;
private int mLastBehavior;
+ private int mLastRequestedVisibleTypes = Type.defaultVisible();
private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
private AppearanceRegion[] mLastStatusBarAppearanceRegions;
private LetterboxDetails[] mLastLetterboxDetails;
@@ -360,8 +354,6 @@ public class DisplayPolicy {
private PointerLocationView mPointerLocationView;
- private int mDisplayCutoutTouchableRegionSize;
-
private RefreshRatePolicy mRefreshRatePolicy;
/**
@@ -1150,71 +1142,9 @@ public class DisplayPolicy {
break;
case TYPE_NAVIGATION_BAR:
mNavigationBar = win;
- final TriConsumer<DisplayFrames, WindowContainer, Rect> navFrameProvider =
- (displayFrames, windowContainer, inOutFrame) -> {
- if (!mNavButtonForcedVisible) {
- final LayoutParams lp =
- win.mAttrs.forRotation(displayFrames.mRotation);
- if (lp.providedInsets != null) {
- for (InsetsFrameProvider provider : lp.providedInsets) {
- if (provider.type != ITYPE_NAVIGATION_BAR) {
- continue;
- }
- InsetsFrameProvider.calculateInsetsFrame(
- displayFrames.mUnrestricted,
- win.getBounds(), displayFrames.mDisplayCutoutSafe,
- inOutFrame, provider.source,
- provider.insetsSize, lp.privateFlags,
- provider.minimalInsetsSizeInDisplayCutoutSafe);
- }
- }
- inOutFrame.inset(win.mGivenContentInsets);
- }
- };
- final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverride =
- new SparseArray<>();
- // For IME, we don't modify the frame.
- imeOverride.put(TYPE_INPUT_METHOD, null);
- mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
- navFrameProvider, imeOverride);
-
- mDisplayContent.setInsetProvider(ITYPE_BOTTOM_MANDATORY_GESTURES, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.top -= mBottomGestureAdditionalInset;
- });
- mDisplayContent.setInsetProvider(ITYPE_LEFT_GESTURES, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- final int leftSafeInset =
- Math.max(displayFrames.mDisplayCutoutSafe.left, 0);
- inOutFrame.left = 0;
- inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mHeight;
- inOutFrame.right = leftSafeInset + mLeftGestureInset;
- });
- mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- final int rightSafeInset =
- Math.min(displayFrames.mDisplayCutoutSafe.right,
- displayFrames.mUnrestricted.right);
- inOutFrame.left = rightSafeInset - mRightGestureInset;
- inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mHeight;
- inOutFrame.right = displayFrames.mWidth;
- });
- mDisplayContent.setInsetProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT, win,
- (displayFrames, windowContainer, inOutFrame) -> {
- if ((win.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0
- || mNavigationBarLetsThroughTaps) {
- inOutFrame.setEmpty();
- }
- });
- mInsetsSourceWindowsExceptIme.add(win);
- if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
}
- // TODO(b/239145252): Temporarily skip the navigation bar as it is still with the hard-coded
- // logic.
- if (attrs.providedInsets != null && attrs.type != TYPE_NAVIGATION_BAR) {
+ if (attrs.providedInsets != null) {
for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
final InsetsFrameProvider provider = attrs.providedInsets[i];
switch (provider.type) {
@@ -1242,24 +1172,8 @@ public class DisplayPolicy {
// The index of the provider and corresponding insets types cannot change at
// runtime as ensured in WMS. Make use of the index in the provider directly
// to access the latest provided size at runtime.
- final int index = i;
final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
- provider.insetsSize != null
- ? (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.inset(win.mGivenContentInsets);
- final LayoutParams lp =
- win.mAttrs.forRotation(displayFrames.mRotation);
- final InsetsFrameProvider ifp =
- win.mAttrs.forRotation(displayFrames.mRotation)
- .providedInsets[index];
- InsetsFrameProvider.calculateInsetsFrame(
- displayFrames.mUnrestricted,
- windowContainer.getBounds(),
- displayFrames.mDisplayCutoutSafe,
- inOutFrame, ifp.source,
- ifp.insetsSize, lp.privateFlags,
- ifp.minimalInsetsSizeInDisplayCutoutSafe);
- } : null;
+ getFrameProvider(win, provider, i);
final InsetsFrameProvider.InsetsSizeOverride[] overrides =
provider.insetsSizeOverrides;
final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
@@ -1267,27 +1181,10 @@ public class DisplayPolicy {
if (overrides != null) {
overrideProviders = new SparseArray<>();
for (int j = overrides.length - 1; j >= 0; j--) {
- final int overrideIndex = j;
final TriConsumer<DisplayFrames, WindowContainer, Rect>
overrideFrameProvider =
- (displayFrames, windowContainer, inOutFrame) -> {
- final LayoutParams lp =
- win.mAttrs.forRotation(
- displayFrames.mRotation);
- final InsetsFrameProvider ifp =
- win.mAttrs.providedInsets[index];
- InsetsFrameProvider.calculateInsetsFrame(
- displayFrames.mUnrestricted,
- windowContainer.getBounds(),
- displayFrames.mDisplayCutoutSafe,
- inOutFrame, ifp.source,
- ifp.insetsSizeOverrides[
- overrideIndex].insetsSize,
- lp.privateFlags,
- null);
- };
- overrideProviders.put(overrides[j].windowType,
- overrideFrameProvider);
+ getOverrideFrameProvider(win, i, j);
+ overrideProviders.put(overrides[j].windowType, overrideFrameProvider);
}
} else {
overrideProviders = null;
@@ -1299,6 +1196,36 @@ public class DisplayPolicy {
}
}
+ @Nullable
+ private TriConsumer<DisplayFrames, WindowContainer, Rect> getFrameProvider(WindowState win,
+ InsetsFrameProvider provider, int index) {
+ if (provider.insetsSize == null && provider.source == SOURCE_FRAME) {
+ 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,
+ windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
+ ifp.source, ifp.insetsSize, lp.privateFlags,
+ ifp.minimalInsetsSizeInDisplayCutoutSafe);
+ };
+ }
+
+ @NonNull
+ private TriConsumer<DisplayFrames, WindowContainer, Rect> getOverrideFrameProvider(
+ WindowState win, int index, int overrideIndex) {
+ return (displayFrames, windowContainer, inOutFrame) -> {
+ final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
+ final InsetsFrameProvider ifp = lp.providedInsets[index];
+ InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
+ windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
+ ifp.source, ifp.insetsSizeOverrides[overrideIndex].insetsSize, lp.privateFlags,
+ null);
+ };
+ }
+
@WindowManagerPolicy.AltBarPosition
private int getAltBarPosition(WindowManager.LayoutParams params) {
switch (params.gravity) {
@@ -1386,16 +1313,6 @@ public class DisplayPolicy {
mInsetsSourceWindowsExceptIme.remove(win);
}
- private int getStatusBarHeight(DisplayFrames displayFrames) {
- int statusBarHeight;
- if (mStatusBar != null) {
- statusBarHeight = mStatusBar.mAttrs.forRotation(displayFrames.mRotation).height;
- } else {
- statusBarHeight = 0;
- }
- return Math.max(statusBarHeight, displayFrames.mDisplayCutoutSafe.top);
- }
-
WindowState getStatusBar() {
return mStatusBar != null ? mStatusBar : mStatusBarAlt;
}
@@ -1551,7 +1468,7 @@ public class DisplayPolicy {
mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
+ UNSPECIFIED_LENGTH, win.getRequestedVisibleTypes(), win.mGlobalScale,
sTmpClientFrames);
final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
final InsetsState state = displayFrames.mInsetsState;
@@ -1598,7 +1515,7 @@ public class DisplayPolicy {
mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
- win.getRequestedVisibilities(), win.mGlobalScale, sTmpClientFrames);
+ win.getRequestedVisibleTypes(), win.mGlobalScale, sTmpClientFrames);
win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
}
@@ -1861,7 +1778,7 @@ public class DisplayPolicy {
if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) {
return false;
}
- return !mTopFullscreenOpaqueWindowState.getRequestedVisibility(ITYPE_STATUS_BAR);
+ return !mTopFullscreenOpaqueWindowState.isRequestedVisible(Type.statusBars());
}
/**
@@ -1892,27 +1809,12 @@ public class DisplayPolicy {
final Resources res = getCurrentUserResources();
final int portraitRotation = displayRotation.getPortraitRotation();
- if (hasStatusBar()) {
- mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
- R.dimen.display_cutout_touchable_region_size);
- } else {
- mDisplayCutoutTouchableRegionSize = 0;
- }
-
mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
- mNavButtonForcedVisible =
- mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
- mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
mNavigationBarAlwaysShowOnSideGesture =
res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
- // This should calculate how much above the frame we accept gestures.
- mBottomGestureAdditionalInset =
- res.getDimensionPixelSize(R.dimen.navigation_bar_gesture_height)
- - getNavigationBarFrameHeight(portraitRotation);
-
updateConfigurationAndScreenSizeDependentBehaviors();
final boolean shouldAttach =
@@ -2221,17 +2123,8 @@ public class DisplayPolicy {
return;
}
- final @InsetsType int restorePositionTypes =
- (controlTarget.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
- ? Type.navigationBars() : 0)
- | (controlTarget.getRequestedVisibility(ITYPE_STATUS_BAR)
- ? Type.statusBars() : 0)
- | (mExtraNavBarAlt != null && controlTarget.getRequestedVisibility(
- ITYPE_EXTRA_NAVIGATION_BAR)
- ? Type.navigationBars() : 0)
- | (mClimateBarAlt != null && controlTarget.getRequestedVisibility(
- ITYPE_CLIMATE_BAR)
- ? Type.statusBars() : 0);
+ final @InsetsType int restorePositionTypes = (Type.statusBars() | Type.navigationBars())
+ & controlTarget.getRequestedVisibleTypes();
if (swipeTarget == mNavigationBar
&& (restorePositionTypes & Type.navigationBars()) != 0) {
@@ -2325,8 +2218,8 @@ public class DisplayPolicy {
navColorWin) | opaqueAppearance;
final int behavior = win.mAttrs.insetsFlags.behavior;
final String focusedApp = win.mAttrs.packageName;
- final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
- || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
+ final boolean isFullscreen = !win.isRequestedVisible(Type.statusBars())
+ || !win.isRequestedVisible(Type.navigationBars());
final AppearanceRegion[] statusBarAppearanceRegions =
new AppearanceRegion[mStatusBarAppearanceRegionList.size()];
mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions);
@@ -2336,11 +2229,12 @@ public class DisplayPolicy {
callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
cause));
}
+ final @InsetsType int requestedVisibleTypes = win.getRequestedVisibleTypes();
final LetterboxDetails[] letterboxDetails = new LetterboxDetails[mLetterboxDetails.size()];
mLetterboxDetails.toArray(letterboxDetails);
if (mLastAppearance == appearance
&& mLastBehavior == behavior
- && mRequestedVisibilities.equals(win.getRequestedVisibilities())
+ && mLastRequestedVisibleTypes == requestedVisibleTypes
&& Objects.equals(mFocusedApp, focusedApp)
&& mLastFocusIsFullscreen == isFullscreen
&& Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)
@@ -2353,9 +2247,12 @@ public class DisplayPolicy {
isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
}
final InsetsVisibilities requestedVisibilities =
- new InsetsVisibilities(win.getRequestedVisibilities());
+ mLastRequestedVisibleTypes == requestedVisibleTypes
+ ? mRequestedVisibilities
+ : toInsetsVisibilities(requestedVisibleTypes);
mLastAppearance = appearance;
mLastBehavior = behavior;
+ mLastRequestedVisibleTypes = requestedVisibleTypes;
mRequestedVisibilities = requestedVisibilities;
mFocusedApp = focusedApp;
mLastFocusIsFullscreen = isFullscreen;
@@ -2366,6 +2263,20 @@ public class DisplayPolicy {
requestedVisibilities, focusedApp, letterboxDetails));
}
+ // TODO (253420890): Remove this when removing mRequestedVisibilities.
+ private static InsetsVisibilities toInsetsVisibilities(@InsetsType int requestedVisibleTypes) {
+ final @InsetsType int defaultVisibleTypes = WindowInsets.Type.defaultVisible();
+ final InsetsVisibilities insetsVisibilities = new InsetsVisibilities();
+ for (@InternalInsetsType int i = InsetsState.SIZE - 1; i >= 0; i--) {
+ @InsetsType int type = InsetsState.toPublicType(i);
+ if ((type & (requestedVisibleTypes ^ defaultVisibleTypes)) != 0) {
+ // We only set the visibility if it is different from the default one.
+ insetsVisibilities.setVisibility(i, (type & requestedVisibleTypes) != 0);
+ }
+ }
+ return insetsVisibilities;
+ }
+
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
mHandler.post(() -> {
StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
@@ -2456,7 +2367,7 @@ public class DisplayPolicy {
appearance = configureNavBarOpacity(appearance, multiWindowTaskVisible,
freeformRootTaskVisible);
- final boolean requestHideNavBar = !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
+ final boolean requestHideNavBar = !win.isRequestedVisible(Type.navigationBars());
final long now = SystemClock.uptimeMillis();
final boolean pendingPanic = mPendingPanicGestureUptime != 0
&& now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 14a1cd011ad6..38eca359c2ba 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -34,6 +34,7 @@ import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
+import android.view.InsetsState;
import android.view.WindowInsets;
import android.window.TaskSnapshot;
@@ -104,7 +105,7 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider
@Override
protected boolean updateClientVisibility(InsetsControlTarget caller) {
boolean changed = super.updateClientVisibility(caller);
- if (changed && caller.getRequestedVisibility(mSource.getType())) {
+ if (changed && caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()))) {
reportImeDrawnForOrganizer(caller);
}
return changed;
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index 287dd74e62c7..d35b7c3d5fbe 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -17,8 +17,7 @@
package com.android.server.wm;
import android.inputmethodservice.InputMethodService;
-import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
/**
@@ -40,10 +39,17 @@ interface InsetsControlTarget {
}
/**
- * @return The requested visibility of this target.
+ * @return {@code true} if any of the {@link InsetsType} is requested visible by this target.
*/
- default boolean getRequestedVisibility(@InternalInsetsType int type) {
- return InsetsState.getDefaultVisibility(type);
+ default boolean isRequestedVisible(@InsetsType int types) {
+ return (WindowInsets.Type.defaultVisible() & types) != 0;
+ }
+
+ /**
+ * @return {@link InsetsType}s which are requested visible by this target.
+ */
+ default @InsetsType int getRequestedVisibleTypes() {
+ return WindowInsets.Type.defaultVisible();
}
/**
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 2de8faf8b086..b9fa80cf2c0f 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -177,8 +177,8 @@ class InsetsPolicy {
: navControlTarget == notificationShade
? getNavControlTarget(topApp, true /* fake */)
: null);
- mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
- mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
+ mStatusBar.updateVisibility(statusControlTarget, Type.statusBars());
+ mNavBar.updateVisibility(navControlTarget, Type.navigationBars());
}
boolean isHidden(@InternalInsetsType int type) {
@@ -455,7 +455,7 @@ class InsetsPolicy {
if (originalImeSource != null) {
final boolean imeVisibility =
- w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME);
+ w.mActivityRecord.mLastImeShown || w.isRequestedVisible(Type.ime());
final InsetsState state = copyState ? new InsetsState(originalState)
: originalState;
final InsetsSource imeSource = new InsetsSource(originalImeSource);
@@ -501,11 +501,11 @@ class InsetsPolicy {
private void checkAbortTransient(InsetsControlTarget caller) {
if (mShowingTransientTypes.size() != 0) {
final IntArray abortTypes = new IntArray();
- final boolean imeRequestedVisible = caller.getRequestedVisibility(ITYPE_IME);
+ final boolean imeRequestedVisible = caller.isRequestedVisible(Type.ime());
for (int i = mShowingTransientTypes.size() - 1; i >= 0; i--) {
final @InternalInsetsType int type = mShowingTransientTypes.get(i);
if ((mStateController.isFakeTarget(type, caller)
- && caller.getRequestedVisibility(type))
+ && caller.isRequestedVisible(InsetsState.toPublicType(type)))
|| (type == ITYPE_NAVIGATION_BAR && imeRequestedVisible)) {
mShowingTransientTypes.remove(i);
abortTypes.add(type);
@@ -552,7 +552,7 @@ class InsetsPolicy {
ComponentName component = focusedWin.mActivityRecord != null
? focusedWin.mActivityRecord.mActivityComponent : null;
mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
- component, focusedWin.getRequestedVisibilities());
+ component, focusedWin.getRequestedVisibleTypes());
return mDisplayContent.mRemoteInsetsControlTarget;
}
if (mPolicy.areSystemBarsForcedShownLw()) {
@@ -612,7 +612,7 @@ class InsetsPolicy {
ComponentName component = focusedWin.mActivityRecord != null
? focusedWin.mActivityRecord.mActivityComponent : null;
mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
- component, focusedWin.getRequestedVisibilities());
+ component, focusedWin.getRequestedVisibleTypes());
return mDisplayContent.mRemoteInsetsControlTarget;
}
if (mPolicy.areSystemBarsForcedShownLw()) {
@@ -734,8 +734,8 @@ class InsetsPolicy {
}
private void updateVisibility(@Nullable InsetsControlTarget controlTarget,
- @InternalInsetsType int type) {
- setVisible(controlTarget == null || controlTarget.getRequestedVisibility(type));
+ @Type.InsetsType int type) {
+ setVisible(controlTarget == null || controlTarget.isRequestedVisible(type));
}
private void setVisible(boolean visible) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 3a8fbbbaa77d..5b205f0bf59d 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -48,6 +48,7 @@ import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.WindowInsets;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -458,8 +459,9 @@ abstract class InsetsSourceProvider {
}
final Point surfacePosition = getWindowFrameSurfacePosition();
mAdapter = new ControlAdapter(surfacePosition);
- if (getSource().getType() == ITYPE_IME) {
- setClientVisible(target.getRequestedVisibility(mSource.getType()));
+ final int type = getSource().getType();
+ if (type == ITYPE_IME) {
+ setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
}
final Transaction t = mDisplayContent.getSyncTransaction();
mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
@@ -472,8 +474,8 @@ abstract class InsetsSourceProvider {
final SurfaceControl leash = mAdapter.mCapturedLeash;
mControlTarget = target;
updateVisibility();
- mControl = new InsetsSourceControl(mSource.getType(), leash, mClientVisible,
- surfacePosition, mInsetsHint);
+ mControl = new InsetsSourceControl(type, leash, mClientVisible, surfacePosition,
+ mInsetsHint);
ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
"InsetsSource Control %s for target %s", mControl, mControlTarget);
@@ -491,7 +493,8 @@ abstract class InsetsSourceProvider {
}
boolean updateClientVisibility(InsetsControlTarget caller) {
- final boolean requestedVisible = caller.getRequestedVisibility(mSource.getType());
+ final boolean requestedVisible =
+ caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()));
if (caller != mControlTarget || requestedVisible == mClientVisible) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index a469c6b39e7f..c19353cb2676 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -17,14 +17,17 @@
package com.android.server.wm;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.function.Function;
/** Reads letterbox configs from resources and controls their overrides at runtime. */
final class LetterboxConfiguration {
@@ -156,34 +159,25 @@ final class LetterboxConfiguration {
// portrait device orientation.
private boolean mIsVerticalReachabilityEnabled;
-
- // Horizontal position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getHorizontalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxHorizontalReachabilityPosition
- private volatile int mLetterboxPositionForHorizontalReachability;
-
- // Vertical position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getVerticalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxVerticalReachabilityPosition
- private volatile int mLetterboxPositionForVerticalReachability;
-
// Whether education is allowed for letterboxed fullscreen apps.
private boolean mIsEducationEnabled;
// Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
+ // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+ @NonNull
+ private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
LetterboxConfiguration(Context systemUiContext) {
+ this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext)));
+ }
+
+ @VisibleForTesting
+ LetterboxConfiguration(Context systemUiContext,
+ LetterboxConfigurationPersister letterboxConfigurationPersister) {
mContext = systemUiContext;
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
R.dimen.config_fixedOrientationLetterboxAspectRatio);
@@ -206,14 +200,14 @@ final class LetterboxConfiguration {
readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
mDefaultPositionForVerticalReachability =
readLetterboxVerticalReachabilityPositionFromConfig(mContext);
- mLetterboxPositionForHorizontalReachability = mDefaultPositionForHorizontalReachability;
- mLetterboxPositionForVerticalReachability = mDefaultPositionForVerticalReachability;
mIsEducationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEducationEnabled);
setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ mLetterboxConfigurationPersister = letterboxConfigurationPersister;
+ mLetterboxConfigurationPersister.start();
}
/**
@@ -653,7 +647,9 @@ final class LetterboxConfiguration {
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getHorizontalMultiplierForReachability() {
- switch (mLetterboxPositionForHorizontalReachability) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ switch (letterboxPositionForHorizontalReachability) {
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
return 0.0f;
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
@@ -662,10 +658,11 @@ final class LetterboxConfiguration {
return 1.0f;
default:
throw new AssertionError(
- "Unexpected letterbox position type: "
- + mLetterboxPositionForHorizontalReachability);
+ "Unexpected letterbox position type: "
+ + letterboxPositionForHorizontalReachability);
}
}
+
/*
* Gets vertical position of a center of the letterboxed app window when reachability
* is enabled specified. 0 corresponds to the top side of the screen and 1 to the bottom side.
@@ -673,7 +670,9 @@ final class LetterboxConfiguration {
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getVerticalMultiplierForReachability() {
- switch (mLetterboxPositionForVerticalReachability) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ switch (letterboxPositionForVerticalReachability) {
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
return 0.0f;
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
@@ -683,7 +682,7 @@ final class LetterboxConfiguration {
default:
throw new AssertionError(
"Unexpected letterbox position type: "
- + mLetterboxPositionForVerticalReachability);
+ + letterboxPositionForVerticalReachability);
}
}
@@ -693,7 +692,7 @@ final class LetterboxConfiguration {
*/
@LetterboxHorizontalReachabilityPosition
int getLetterboxPositionForHorizontalReachability() {
- return mLetterboxPositionForHorizontalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
}
/*
@@ -702,7 +701,7 @@ final class LetterboxConfiguration {
*/
@LetterboxVerticalReachabilityPosition
int getLetterboxPositionForVerticalReachability() {
- return mLetterboxPositionForVerticalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
}
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
@@ -742,9 +741,8 @@ final class LetterboxConfiguration {
* right side.
*/
void movePositionForHorizontalReachabilityToNextRightStop() {
- mLetterboxPositionForHorizontalReachability = Math.min(
- mLetterboxPositionForHorizontalReachability + 1,
- LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT);
+ updatePositionForHorizontalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
}
/**
@@ -752,8 +750,7 @@ final class LetterboxConfiguration {
* side.
*/
void movePositionForHorizontalReachabilityToNextLeftStop() {
- mLetterboxPositionForHorizontalReachability =
- Math.max(mLetterboxPositionForHorizontalReachability - 1, 0);
+ updatePositionForHorizontalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -761,9 +758,8 @@ final class LetterboxConfiguration {
* side.
*/
void movePositionForVerticalReachabilityToNextBottomStop() {
- mLetterboxPositionForVerticalReachability = Math.min(
- mLetterboxPositionForVerticalReachability + 1,
- LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM);
+ updatePositionForVerticalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
}
/**
@@ -771,8 +767,7 @@ final class LetterboxConfiguration {
* side.
*/
void movePositionForVerticalReachabilityToNextTopStop() {
- mLetterboxPositionForVerticalReachability =
- Math.max(mLetterboxPositionForVerticalReachability - 1, 0);
+ updatePositionForVerticalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -822,4 +817,26 @@ final class LetterboxConfiguration {
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
+ /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
+ private void updatePositionForHorizontalReachability(
+ Function<Integer, Integer> newHorizonalPositionFun) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int nextHorizontalPosition = newHorizonalPositionFun.apply(
+ letterboxPositionForHorizontalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ nextHorizontalPosition);
+ }
+
+ /** Calculates a new letterboxPositionForVerticalReachability value and updates the store */
+ private void updatePositionForVerticalReachability(
+ Function<Integer, Integer> newVerticalPositionFun) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int nextVerticalPosition = newVerticalPositionFun.apply(
+ letterboxPositionForVerticalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ nextVerticalPosition);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
new file mode 100644
index 000000000000..70639b16c828
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -0,0 +1,259 @@
+/*
+ * 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;
+
+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.content.Context;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition;
+import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition;
+import com.android.server.wm.nano.WindowManagerProtos;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Persists the values of letterboxPositionForHorizontalReachability and
+ * letterboxPositionForVerticalReachability for {@link LetterboxConfiguration}.
+ */
+class LetterboxConfigurationPersister {
+
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
+
+ @VisibleForTesting
+ static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+
+ private final Context mContext;
+ private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
+ private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
+
+ // Horizontal position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getHorizontalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxHorizontalReachabilityPosition
+ private volatile int mLetterboxPositionForHorizontalReachability;
+
+ // Vertical position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getVerticalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxVerticalReachabilityPosition
+ private volatile int mLetterboxPositionForVerticalReachability;
+
+ @NonNull
+ private final AtomicFile mConfigurationFile;
+
+ @Nullable
+ private final Consumer<String> mCompletionCallback;
+
+ @NonNull
+ private final PersisterQueue mPersisterQueue;
+
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier) {
+ this(systemUiContext, defaultHorizontalReachabilitySupplier,
+ defaultVerticalReachabilitySupplier,
+ Environment.getDataSystemDirectory(), new PersisterQueue(),
+ /* completionCallback */ null);
+ }
+
+ @VisibleForTesting
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier, File configFolder,
+ PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
+ mContext = systemUiContext.createDeviceProtectedStorageContext();
+ mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
+ mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
+ mCompletionCallback = completionCallback;
+ final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+ mConfigurationFile = new AtomicFile(prefFiles);
+ mPersisterQueue = persisterQueue;
+ readCurrentConfiguration();
+ }
+
+ /**
+ * Startes the persistence queue
+ */
+ void start() {
+ mPersisterQueue.startPersisting();
+ }
+
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForHorizontalReachability(
+ int letterboxPositionForHorizontalReachability) {
+ if (mLetterboxPositionForHorizontalReachability
+ != letterboxPositionForHorizontalReachability) {
+ mLetterboxPositionForHorizontalReachability =
+ letterboxPositionForHorizontalReachability;
+ updateConfiguration();
+ }
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForVerticalReachability(
+ int letterboxPositionForVerticalReachability) {
+ if (mLetterboxPositionForVerticalReachability != letterboxPositionForVerticalReachability) {
+ mLetterboxPositionForVerticalReachability = letterboxPositionForVerticalReachability;
+ updateConfiguration();
+ }
+ }
+
+ @VisibleForTesting
+ void useDefaultValue() {
+ mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get();
+ mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get();
+ }
+
+ private void readCurrentConfiguration() {
+ FileInputStream fis = null;
+ try {
+ fis = mConfigurationFile.openRead();
+ byte[] protoData = readInputStream(fis);
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ WindowManagerProtos.LetterboxProto.parseFrom(protoData);
+ mLetterboxPositionForHorizontalReachability =
+ letterboxData.letterboxPositionForHorizontalReachability;
+ mLetterboxPositionForVerticalReachability =
+ letterboxData.letterboxPositionForVerticalReachability;
+ } catch (IOException ioe) {
+ Slog.e(TAG,
+ "Error reading from LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ useDefaultValue();
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ useDefaultValue();
+ Slog.e(TAG, "Error reading from LetterboxConfigurationPersister ", e);
+ }
+ }
+ }
+ }
+
+ private void updateConfiguration() {
+ mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile,
+ mLetterboxPositionForHorizontalReachability,
+ mLetterboxPositionForVerticalReachability,
+ mCompletionCallback), /* flush */ true);
+ }
+
+ private static byte[] readInputStream(InputStream in) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ byte[] buffer = new byte[1024];
+ int size = in.read(buffer);
+ while (size > 0) {
+ outputStream.write(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ return outputStream.toByteArray();
+ } finally {
+ outputStream.close();
+ }
+ }
+
+ private static class UpdateValuesCommand implements
+ PersisterQueue.WriteQueueItem<UpdateValuesCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToUpdate;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+
+ private final int mHorizontalReachability;
+ private final int mVerticalReachability;
+
+ UpdateValuesCommand(@NonNull AtomicFile fileToUpdate,
+ int horizontalReachability, int verticalReachability,
+ @Nullable Consumer<String> onComplete) {
+ mFileToUpdate = fileToUpdate;
+ mHorizontalReachability = horizontalReachability;
+ mVerticalReachability = verticalReachability;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ new WindowManagerProtos.LetterboxProto();
+ letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability;
+ letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability;
+ final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData);
+
+ FileOutputStream fos = null;
+ try {
+ fos = mFileToUpdate.startWrite();
+ fos.write(bytes);
+ mFileToUpdate.finishWrite(fos);
+ } catch (IOException ioe) {
+ mFileToUpdate.failWrite(fos);
+ Slog.e(TAG,
+ "Error writing to LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ } finally {
+ if (mOnComplete != null) {
+ mOnComplete.accept("UpdateValuesCommand");
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 8db5289f8b45..d34e610fa0fd 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -323,11 +323,11 @@ class RemoteAnimationController implements DeathRecipient {
mService.closeSurfaceTransaction("RemoteAnimationController#finished");
mIsFinishing = false;
}
+ // Reset input for all activities when the remote animation is finished.
+ final Consumer<ActivityRecord> updateActivities =
+ activity -> activity.setDropInputForAnimation(false);
+ mDisplayContent.forAllActivities(updateActivities);
}
- // Reset input for all activities when the remote animation is finished.
- final Consumer<ActivityRecord> updateActivities =
- activity -> activity.setDropInputForAnimation(false);
- mDisplayContent.forAllActivities(updateActivities);
setRunningRemoteAnimation(false);
ProtoLog.i(WM_DEBUG_REMOTE_ANIMATIONS, "Finishing remote animation");
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 9660fe2142ae..b4821816977f 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -69,10 +69,11 @@ import android.view.IWindowSessionCallback;
import android.view.InputChannel;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import android.window.OnBackInvokedCallbackInfo;
@@ -117,7 +118,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
private float mLastReportedAnimatorScale;
private String mPackageName;
private String mRelayoutTag;
- private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
final boolean mSetsUnrestrictedKeepClearAreas;
@@ -196,23 +196,23 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
+ UserHandle.getUserId(mUid), requestedVisibleTypes, outInputChannel, outInsetsState,
outActiveControls, outAttachedFrame, outSizeCompatScale);
}
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
+ int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
- requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
+ requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
outAttachedFrame, outSizeCompatScale);
}
@@ -221,8 +221,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
int viewVisibility, int displayId, InsetsState outInsetsState, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
- UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */,
- outInsetsState, mDummyControls, outAttachedFrame, outSizeCompatScale);
+ UserHandle.getUserId(mUid), WindowInsets.Type.defaultVisible(),
+ null /* outInputChannel */, outInsetsState, mDummyControls, outAttachedFrame,
+ outSizeCompatScale);
}
@Override
@@ -683,12 +684,12 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) {
+ public void updateRequestedVisibleTypes(IWindow window, @InsetsType int requestedVisibleTypes) {
synchronized (mService.mGlobalLock) {
final WindowState windowState = mService.windowForClientLocked(this, window,
false /* throwOnError */);
if (windowState != null) {
- windowState.setRequestedVisibilities(visibilities);
+ windowState.setRequestedVisibleTypes(requestedVisibleTypes);
windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(windowState);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e29e3a2abc13..435ab9723c1c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3513,7 +3513,7 @@ class Task extends TaskFragment {
final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION);
if (topMainWin != null) {
info.mainWindowLayoutParams = topMainWin.getAttrs();
- info.requestedVisibilities.set(topMainWin.getRequestedVisibilities());
+ info.requestedVisibleTypes = topMainWin.getRequestedVisibleTypes();
}
}
// If the developer has persist a different configuration, we need to override it to the
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 9306749f17b9..29c98b9f8b01 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -587,7 +587,7 @@ class TaskSnapshotController {
final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
- mHighResTaskSnapshotScale, insetsState);
+ mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
final int taskWidth = taskBounds.width();
final int taskHeight = taskBounds.height();
final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
@@ -750,12 +750,12 @@ class TaskSnapshotController {
private final int mWindowFlags;
private final int mWindowPrivateFlags;
private final float mScale;
- private final InsetsState mInsetsState;
+ private final @Type.InsetsType int mRequestedVisibleTypes;
private final Rect mSystemBarInsets = new Rect();
SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
ActivityManager.TaskDescription taskDescription, float scale,
- InsetsState insetsState) {
+ @Type.InsetsType int requestedVisibleTypes) {
mWindowFlags = windowFlags;
mWindowPrivateFlags = windowPrivateFlags;
mScale = scale;
@@ -774,7 +774,7 @@ class TaskSnapshotController {
&& context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
mStatusBarPaint.setColor(mStatusBarColor);
mNavigationBarPaint.setColor(mNavigationBarColor);
- mInsetsState = insetsState;
+ mRequestedVisibleTypes = requestedVisibleTypes;
}
void setInsets(Rect systemBarInsets) {
@@ -785,7 +785,7 @@ class TaskSnapshotController {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) {
+ mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
return (int) (mSystemBarInsets.top * mScale);
} else {
return 0;
@@ -796,7 +796,7 @@ class TaskSnapshotController {
final boolean forceBarBackground =
(mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mInsetsState, mNavigationBarColor, mWindowFlags, forceBarBackground);
+ mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
}
void drawDecors(Canvas c) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b2c8b7ab98d7..80c980310664 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1568,7 +1568,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
change.setFlags(info.getChangeFlags(target));
+
final Task task = target.asTask();
+ final TaskFragment taskFragment = target.asTaskFragment();
+ final ActivityRecord activityRecord = target.asActivityRecord();
+
if (task != null) {
final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
task.fillTaskInfo(tinfo);
@@ -1602,12 +1606,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
change.setEndRelOffset(bounds.left - parentBounds.left,
bounds.top - parentBounds.top);
int endRotation = target.getWindowConfiguration().getRotation();
- final ActivityRecord activityRecord = target.asActivityRecord();
if (activityRecord != null) {
- final Task arTask = activityRecord.getTask();
- final int backgroundColor = ColorUtils.setAlphaComponent(
- arTask.getTaskDescription().getBackgroundColor(), 255);
- change.setBackgroundColor(backgroundColor);
// TODO(b/227427984): Shell needs to aware letterbox.
// Always use parent bounds of activity because letterbox area (e.g. fixed aspect
// ratio or size compat mode) should be included in the animation.
@@ -1620,6 +1619,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
} else {
change.setEndAbsBounds(bounds);
}
+
+ if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) {
+ // Set background color to Task theme color for activity and embedded TaskFragment
+ // in case we want to show background during the animation.
+ final Task parentTask = activityRecord != null
+ ? activityRecord.getTask()
+ : taskFragment.getTask();
+ final int backgroundColor = ColorUtils.setAlphaComponent(
+ parentTask.getTaskDescription().getBackgroundColor(), 255);
+ change.setBackgroundColor(backgroundColor);
+ }
+
change.setRotation(info.mRotation, endRotation);
if (info.mSnapshot != null) {
change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index c4c66d8c9c3c..0b5de85c5cab 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -41,6 +41,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.AppTransition.MAX_APP_TRANSITION_DURATION;
import static com.android.server.wm.AppTransition.isActivityTransitOld;
+import static com.android.server.wm.AppTransition.isTaskFragmentTransitOld;
import static com.android.server.wm.AppTransition.isTaskTransitOld;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.IdentifierProto.HASH_CODE;
@@ -2999,10 +3000,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// {@link Activity#overridePendingTransition(int, int, int)}.
@ColorInt int backdropColor = 0;
if (controller.isFromActivityEmbedding()) {
- final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
- final Animation a = animAttr != 0
- ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
- showBackdrop = a != null && a.getShowBackdrop();
+ if (isChanging) {
+ // 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;
+ } else {
+ // Check whether or not to show backdrop for open/close transition.
+ final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
+ final Animation a = animAttr != 0
+ ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+ showBackdrop = a != null && a.getShowBackdrop();
+ }
backdropColor = appTransition.getNextAppTransitionBackgroundColor();
}
final Rect localBounds = new Rect(mTmpRect);
@@ -3105,9 +3113,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ // Check if the animation requests to show background color for Activity and embedded
+ // TaskFragment.
final ActivityRecord activityRecord = asActivityRecord();
- if (activityRecord != null && isActivityTransitOld(transit)
- && adapter.getShowBackground()) {
+ final TaskFragment taskFragment = asTaskFragment();
+ if (adapter.getShowBackground()
+ // Check if it is Activity transition.
+ && ((activityRecord != null && isActivityTransitOld(transit))
+ // Check if it is embedded TaskFragment transition.
+ || (taskFragment != null && taskFragment.isEmbedded()
+ && isTaskFragmentTransitOld(transit)))) {
final @ColorInt int backgroundColorForTransition;
if (adapter.getBackgroundColor() != 0) {
// If available use the background color provided through getBackgroundColor
@@ -3117,9 +3132,11 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// Otherwise default to the window's background color if provided through
// the theme as the background color for the animation - the top most window
// with a valid background color and showBackground set takes precedence.
- final Task arTask = activityRecord.getTask();
+ final Task parentTask = activityRecord != null
+ ? activityRecord.getTask()
+ : taskFragment.getTask();
backgroundColorForTransition = ColorUtils.setAlphaComponent(
- arTask.getTaskDescription().getBackgroundColor(), 255);
+ parentTask.getTaskDescription().getBackgroundColor(), 255);
}
animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index c9d3dac104de..848c231bb568 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -266,9 +266,9 @@ import android.view.InputApplicationHandle;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputWindowHandle;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
@@ -283,6 +283,7 @@ import android.view.TaskTransitionSpec;
import android.view.View;
import android.view.WindowContentFrameStats;
import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManager.LayoutParams;
@@ -1409,7 +1410,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
- int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
+ int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
@@ -1635,7 +1636,7 @@ public class WindowManagerService extends IWindowManager.Stub
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid,
callingPid);
- win.setRequestedVisibilities(requestedVisibilities);
+ win.setRequestedVisibleTypes(requestedVisibleTypes);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
if (res != ADD_OKAY) {
@@ -2277,6 +2278,27 @@ public class WindowManagerService extends IWindowManager.Stub
"Insets types can not be changed after the window is "
+ "added.");
}
+ final InsetsFrameProvider.InsetsSizeOverride[] overrides =
+ win.mAttrs.providedInsets[i].insetsSizeOverrides;
+ final InsetsFrameProvider.InsetsSizeOverride[] newOverrides =
+ attrs.providedInsets[i].insetsSizeOverrides;
+ if (!(overrides == null && newOverrides == null)) {
+ if (overrides == null || newOverrides == null
+ || (overrides.length != newOverrides.length)) {
+ throw new IllegalArgumentException(
+ "Insets override types can not be changed after the "
+ + "window is added.");
+ } else {
+ final int overrideTypes = overrides.length;
+ for (int j = 0; j < overrideTypes; j++) {
+ if (overrides[j].windowType != newOverrides[j].windowType) {
+ throw new IllegalArgumentException(
+ "Insets override types can not be changed after"
+ + " the window is added.");
+ }
+ }
+ }
+ }
}
}
}
@@ -4428,7 +4450,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void updateDisplayWindowRequestedVisibilities(int displayId, InsetsVisibilities vis) {
+ public void updateDisplayWindowRequestedVisibleTypes(
+ int displayId, @InsetsType int requestedVisibleTypes) {
if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
@@ -4440,7 +4463,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (dc == null || dc.mRemoteInsetsControlTarget == null) {
return;
}
- dc.mRemoteInsetsControlTarget.setRequestedVisibilities(vis);
+ dc.mRemoteInsetsControlTarget.setRequestedVisibleTypes(requestedVisibleTypes);
dc.getInsetsStateController().onInsetsModified(dc.mRemoteInsetsControlTarget);
}
} finally {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 36389eacf27c..6d750a469f8c 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -30,7 +30,6 @@ import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_INVALID;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.ViewRootImpl.LOCAL_LAYOUT;
@@ -41,6 +40,7 @@ import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_
import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
+import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
@@ -233,7 +233,6 @@ import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -767,7 +766,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private boolean mIsDimming = false;
- private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+ private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
/**
* Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
@@ -833,31 +832,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private int mSurfaceTranslationY;
- /**
- * Returns the visibility of the given {@link InternalInsetsType type} requested by the client.
- *
- * @param type the given {@link InternalInsetsType type}.
- * @return {@code true} if the type is requested visible.
- */
@Override
- public boolean getRequestedVisibility(@InternalInsetsType int type) {
- return mRequestedVisibilities.getVisibility(type);
+ public boolean isRequestedVisible(@InsetsType int types) {
+ return (mRequestedVisibleTypes & types) != 0;
}
/**
- * Returns all the requested visibilities.
+ * Returns requested visible types of insets.
*
- * @return an {@link InsetsVisibilities} as the requested visibilities.
+ * @return an integer as the requested visible insets types.
*/
- InsetsVisibilities getRequestedVisibilities() {
- return mRequestedVisibilities;
+ @Override
+ public @InsetsType int getRequestedVisibleTypes() {
+ return mRequestedVisibleTypes;
}
/**
- * @see #getRequestedVisibility(int)
+ * @see #getRequestedVisibleTypes()
*/
- void setRequestedVisibilities(InsetsVisibilities visibilities) {
- mRequestedVisibilities.set(visibilities);
+ void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes) {
+ if (mRequestedVisibleTypes != requestedVisibleTypes) {
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ }
+ }
+
+ @VisibleForTesting
+ void setRequestedVisibleTypes(@InsetsType int requestedVisibleTypes, @InsetsType int mask) {
+ setRequestedVisibleTypes(mRequestedVisibleTypes & ~mask | requestedVisibleTypes & mask);
}
/**
@@ -973,7 +974,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean isImplicitlyExcludingAllSystemGestures() {
final boolean stickyHideNav =
mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- && !getRequestedVisibility(ITYPE_NAVIGATION_BAR);
+ && !isRequestedVisible(navigationBars());
return stickyHideNav && mWmService.mConstants.mSystemGestureExcludedByPreQStickyImmersive
&& mActivityRecord != null && mActivityRecord.mTargetSdk < Build.VERSION_CODES.Q;
}
@@ -1718,7 +1719,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
InsetsState getInsetsStateWithVisibilityOverride() {
final InsetsState state = new InsetsState(getInsetsState());
for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
- final boolean requestedVisible = getRequestedVisibility(type);
+ final boolean requestedVisible = isRequestedVisible(InsetsState.toPublicType(type));
InsetsSource source = state.peekSource(type);
if (source != null && source.isVisible() != requestedVisible) {
source = new InsetsSource(source);
@@ -4436,9 +4437,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas
+ ", unrestricted=" + mUnrestrictedKeepClearAreas);
if (dumpAll) {
- final String visibilityString = mRequestedVisibilities.toString();
- if (!visibilityString.isEmpty()) {
- pw.println(prefix + "Requested visibilities: " + visibilityString);
+ if (mRequestedVisibleTypes != WindowInsets.Type.defaultVisible()) {
+ pw.println(prefix + "Requested non-default-visibility types: "
+ + WindowInsets.Type.toString(
+ mRequestedVisibleTypes ^ WindowInsets.Type.defaultVisible()));
}
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3f380e7914d0..0d872370dcdc 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@ static struct {
jmethodID notifyFocusChanged;
jmethodID notifySensorEvent;
jmethodID notifySensorAccuracy;
+ jmethodID notifyStylusGestureStarted;
jmethodID notifyVibratorState;
jmethodID filterInputEvent;
jmethodID interceptKeyBeforeQueueing;
@@ -299,6 +300,7 @@ public:
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
void setCustomPointerIcon(const SpriteIcon& icon);
void setMotionClassifierEnabled(bool enabled);
+ std::optional<std::string> getBluetoothAddress(int32_t deviceId);
/* --- InputReaderPolicyInterface implementation --- */
@@ -312,6 +314,7 @@ public:
int32_t surfaceRotation) override;
TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr);
+ void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -370,37 +373,37 @@ private:
Mutex mLock;
struct Locked {
// Display size information.
- std::vector<DisplayViewport> viewports;
+ std::vector<DisplayViewport> viewports{};
// True if System UI is less noticeable.
- bool systemUiLightsOut;
+ bool systemUiLightsOut{false};
// Pointer speed.
- int32_t pointerSpeed;
+ int32_t pointerSpeed{0};
// Pointer acceleration.
- float pointerAcceleration;
+ float pointerAcceleration{android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION};
// True if pointer gestures are enabled.
- bool pointerGesturesEnabled;
+ bool pointerGesturesEnabled{true};
// Show touches feature enable/disable.
- bool showTouches;
+ bool showTouches{false};
// The latest request to enable or disable Pointer Capture.
- PointerCaptureRequest pointerCaptureRequest;
+ PointerCaptureRequest pointerCaptureRequest{};
// Sprite controller singleton, created on first use.
- sp<SpriteController> spriteController;
+ sp<SpriteController> spriteController{};
// Pointer controller singleton, created and destroyed as needed.
- std::weak_ptr<PointerController> pointerController;
+ std::weak_ptr<PointerController> pointerController{};
// Input devices to be disabled
- std::set<int32_t> disabledInputDevices;
+ std::set<int32_t> disabledInputDevices{};
// Associated Pointer controller display.
- int32_t pointerDisplayId;
+ int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT};
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
@@ -419,16 +422,6 @@ NativeInputManager::NativeInputManager(jobject serviceObj, const sp<Looper>& loo
mServiceObj = env->NewGlobalRef(serviceObj);
- {
- AutoMutex _l(mLock);
- mLocked.systemUiLightsOut = false;
- mLocked.pointerSpeed = 0;
- mLocked.pointerAcceleration = android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION;
- mLocked.pointerGesturesEnabled = true;
- mLocked.showTouches = false;
- mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
- }
- mInteractive = true;
InputManager* im = new InputManager(this, this);
mInputManager = im;
defaultServiceManager()->addService(String16("inputflinger"), im);
@@ -1177,6 +1170,13 @@ TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
return transform;
}
+void NativeInputManager::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
+ JNIEnv* env = jniEnv();
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyStylusGestureStarted, deviceId,
+ eventTime);
+ checkAndClearExceptionFromCallback(env, "notifyStylusGestureStarted");
+}
+
bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
ATRACE_CALL();
jobject inputEventObj;
@@ -1487,6 +1487,10 @@ void NativeInputManager::setMotionClassifierEnabled(bool enabled) {
mInputManager->getProcessor().setMotionClassifierEnabled(enabled);
}
+std::optional<std::string> NativeInputManager::getBluetoothAddress(int32_t deviceId) {
+ return mInputManager->getReader().getBluetoothAddress(deviceId);
+}
+
bool NativeInputManager::isPerDisplayTouchModeEnabled() {
JNIEnv* env = jniEnv();
jboolean enabled =
@@ -2326,6 +2330,12 @@ static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint d
im->setPointerDisplayId(displayId);
}
+static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ const auto address = im->getBluetoothAddress(deviceId);
+ return address ? env->NewStringUTF(address->c_str()) : nullptr;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2408,6 +2418,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"flushSensor", "(II)Z", (void*)nativeFlushSensor},
{"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
{"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
+ {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
};
#define FIND_CLASS(var, className) \
@@ -2469,6 +2480,9 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_METHOD_ID(gServiceClassInfo.notifySensorAccuracy, clazz, "notifySensorAccuracy", "(III)V");
+ GET_METHOD_ID(gServiceClassInfo.notifyStylusGestureStarted, clazz, "notifyStylusGestureStarted",
+ "(IJ)V");
+
GET_METHOD_ID(gServiceClassInfo.notifyVibratorState, clazz, "notifyVibratorState", "(IZ)V");
GET_METHOD_ID(gServiceClassInfo.notifyNoFocusedWindowAnr, clazz, "notifyNoFocusedWindowAnr",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b74fedf3ff46..593e64828ffc 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,6 +154,7 @@ import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.ApexSystemServiceInfo;
+import com.android.server.pm.BackgroundInstallControlService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -2469,6 +2470,10 @@ public final class SystemServer implements Dumpable {
t.traceBegin("StartMediaMetricsManager");
mSystemServiceManager.startService(MediaMetricsManagerService.class);
t.traceEnd();
+
+ t.traceBegin("StartBackgroundInstallControlService");
+ mSystemServiceManager.startService(BackgroundInstallControlService.class);
+ t.traceEnd();
}
t.traceBegin("StartMediaProjectionManager");
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 ba414cb593ef..5b7b8f4ca21f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.doReturn;
import android.annotation.NonNull;
@@ -43,6 +44,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
+import android.os.BundleMerger;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.provider.Settings;
@@ -57,12 +59,15 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnitRunner;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastQueueModernImplTest {
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
+ private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
@Mock ActivityManagerService mAms;
@Mock ProcessRecord mProcess;
@@ -87,6 +92,10 @@ public class BroadcastQueueModernImplTest {
mHandlerThread.start();
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+ mConstants.DELAY_URGENT_MILLIS = -120_000;
+ mConstants.DELAY_NORMAL_MILLIS = 10_000;
+ mConstants.DELAY_CACHED_MILLIS = 120_000;
+
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
mConstants, mConstants);
@@ -467,6 +476,62 @@ public class BroadcastQueueModernImplTest {
List.of(musicVolumeChanged, alarmVolumeChanged, timeTick));
}
+ /**
+ * Verify that sending a broadcast with DELIVERY_GROUP_POLICY_MERGED works as expected.
+ */
+ @Test
+ public void testDeliveryGroupPolicy_merged() {
+ final BundleMerger extrasMerger = new BundleMerger();
+ extrasMerger.setMergeStrategy(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ BundleMerger.STRATEGY_ARRAY_APPEND);
+
+ final Intent packageChangedForUid = createPackageChangedIntent(TEST_UID,
+ List.of("com.testuid.component1"));
+ final BroadcastOptions optionsPackageChangedForUid = BroadcastOptions.makeBasic();
+ optionsPackageChangedForUid.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID));
+ optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ final Intent secondPackageChangedForUid = createPackageChangedIntent(TEST_UID,
+ List.of("com.testuid.component2", "com.testuid.component3"));
+
+ final Intent packageChangedForUid2 = createPackageChangedIntent(TEST_UID2,
+ List.of("com.testuid2.component1"));
+ final BroadcastOptions optionsPackageChangedForUid2 = BroadcastOptions.makeBasic();
+ optionsPackageChangedForUid.setDeliveryGroupPolicy(
+ BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED);
+ optionsPackageChangedForUid.setDeliveryGroupKey("package", String.valueOf(TEST_UID2));
+ optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
+
+ // Halt all processing so that we get a consistent view
+ mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid,
+ optionsPackageChangedForUid));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid2,
+ optionsPackageChangedForUid2));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(secondPackageChangedForUid,
+ optionsPackageChangedForUid));
+
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final Intent expectedPackageChangedForUid = createPackageChangedIntent(TEST_UID,
+ List.of("com.testuid.component2", "com.testuid.component3",
+ "com.testuid.component1"));
+ // Verify that packageChangedForUid and secondPackageChangedForUid broadcasts
+ // have been merged.
+ verifyPendingRecords(queue, List.of(packageChangedForUid2, expectedPackageChangedForUid));
+ }
+
+ private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
+ final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
+ packageChangedIntent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+ componentNameList.toArray());
+ return packageChangedIntent;
+ }
+
private void verifyPendingRecords(BroadcastProcessQueue queue,
List<Intent> intents) {
for (int i = 0; i < intents.size(); i++) {
@@ -477,9 +542,45 @@ public class BroadcastQueueModernImplTest {
+ ", actual_extras=" + actualIntent.getExtras()
+ ", expected_extras=" + expectedIntent.getExtras();
assertTrue(errMsg, actualIntent.filterEquals(expectedIntent));
- assertTrue(errMsg, Bundle.kindofEquals(
- actualIntent.getExtras(), expectedIntent.getExtras()));
+ assertBundleEquals(expectedIntent.getExtras(), actualIntent.getExtras());
}
assertTrue(queue.isEmpty());
}
+
+ private void assertBundleEquals(Bundle expected, Bundle actual) {
+ final String errMsg = "expected=" + expected + ", actual=" + actual;
+ if (expected == actual) {
+ return;
+ } else if (expected == null || actual == null) {
+ fail(errMsg);
+ }
+ if (!expected.keySet().equals(actual.keySet())) {
+ fail(errMsg);
+ }
+ for (String key : expected.keySet()) {
+ final Object expectedValue = expected.get(key);
+ final Object actualValue = actual.get(key);
+ if (expectedValue == actualValue) {
+ continue;
+ } else if (expectedValue == null || actualValue == null) {
+ fail(errMsg);
+ }
+ assertEquals(errMsg, expectedValue.getClass(), actualValue.getClass());
+ if (expectedValue.getClass().isArray()) {
+ assertEquals(errMsg, Array.getLength(expectedValue), Array.getLength(actualValue));
+ for (int i = 0; i < Array.getLength(expectedValue); ++i) {
+ assertEquals(errMsg, Array.get(expectedValue, i), Array.get(actualValue, i));
+ }
+ } else if (expectedValue instanceof ArrayList) {
+ final ArrayList<?> expectedList = (ArrayList<?>) expectedValue;
+ final ArrayList<?> actualList = (ArrayList<?>) actualValue;
+ assertEquals(errMsg, expectedList.size(), actualList.size());
+ for (int i = 0; i < expectedList.size(); ++i) {
+ assertEquals(errMsg, expectedList.get(i), actualList.get(i));
+ }
+ } else {
+ assertEquals(errMsg, expectedValue, actualValue);
+ }
+ }
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index d9a26c68f3ed..e1a4c1dd7256 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -888,7 +888,7 @@ public class BroadcastQueueTest {
}) {
// Confirm expected OOM adjustments; we were invoked once to upgrade
// and once to downgrade
- assertEquals(ActivityManager.PROCESS_STATE_RECEIVER,
+ assertEquals(String.valueOf(receiverApp), ActivityManager.PROCESS_STATE_RECEIVER,
receiverApp.mState.getReportedProcState());
verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
@@ -897,8 +897,8 @@ public class BroadcastQueueTest {
// cold-started apps to be thawed, but the modern stack does
} else {
// Confirm that app was thawed
- verify(mAms.mOomAdjuster.mCachedAppOptimizer).unfreezeTemporarily(eq(receiverApp),
- eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+ verify(mAms.mOomAdjuster.mCachedAppOptimizer, atLeastOnce()).unfreezeTemporarily(
+ eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
// Confirm that we added package to process
verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1599,4 +1599,39 @@ public class BroadcastQueueTest {
assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
assertTrue(mQueue.isBeyondBarrierLocked(afterSecond));
}
+
+ /**
+ * Verify that we OOM adjust for manifest receivers.
+ */
+ @Test
+ public void testOomAdjust_Manifest() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_RED))));
+
+ waitForIdle();
+ verify(mAms, atLeastOnce()).enqueueOomAdjTargetLocked(any());
+ }
+
+ /**
+ * Verify that we never OOM adjust for registered receivers.
+ */
+ @Test
+ public void testOomAdjust_Registered() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp),
+ makeRegisteredReceiver(receiverApp),
+ makeRegisteredReceiver(receiverApp))));
+
+ waitForIdle();
+ verify(mAms, never()).enqueueOomAdjTargetLocked(any());
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index f46877e5a8c6..25473477e8b9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -16,10 +16,19 @@
package com.android.server.job;
+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.spyOn;
import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_EJ;
import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_REGULAR;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -47,15 +56,6 @@ import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
-
import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
import com.android.server.LocalServices;
@@ -73,6 +73,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.util.ArrayList;
import java.util.List;
@@ -124,6 +125,7 @@ public final class JobConcurrencyManagerTest {
mMockingSession = mockitoSession()
.initMocks(this)
.mockStatic(AppGlobals.class)
+ .spyStatic(DeviceConfig.class)
.strictness(Strictness.LENIENT)
.startMocking();
final JobSchedulerService jobSchedulerService = mock(JobSchedulerService.class);
@@ -134,6 +136,8 @@ public final class JobConcurrencyManagerTest {
when(mContext.getResources()).thenReturn(mResources);
doReturn(mContext).when(jobSchedulerService).getTestableContext();
mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+ doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build())
+ .when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER)));
mPendingJobQueue = new PendingJobQueue();
doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
@@ -595,7 +599,6 @@ public final class JobConcurrencyManagerTest {
}
private void updateDeviceConfig() throws Exception {
- DeviceConfig.setProperties(mConfigBuilder.build());
mJobConcurrencyManager.updateConfigLocked();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index e28d331b770b..28c78b20aae2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -22,6 +22,7 @@ import android.content.pm.SharedLibraryInfo
import android.content.pm.VersionedPackage
import android.os.Build
import android.os.storage.StorageManager
+import android.os.UserHandle
import android.util.ArrayMap
import android.util.PackageUtils
import com.android.server.SystemConfig.SharedLibraryEntry
@@ -222,10 +223,11 @@ class SharedLibrariesImplTest {
val parsedPackage = pair.second as ParsedPackage
val scanRequest = ScanRequest(parsedPackage, null, null, null, null,
null, null, null, 0, 0, false, null, null)
- val scanResult = ScanResult(scanRequest, true, null, null, false, 0, null, null, null)
+ val scanResult = ScanResult(scanRequest, null, null, false, 0, null, null, null)
+ var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult)
val latestInfoSetting =
- mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(scanResult)!!
+ mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(installRequest)!!
assertThat(latestInfoSetting).isNotNull()
assertThat(latestInfoSetting.packageName).isEqualTo(STATIC_LIB_PACKAGE_NAME)
@@ -305,10 +307,11 @@ class SharedLibrariesImplTest {
@Test
fun getAllowedSharedLibInfos_withStaticSharedLibInfo() {
val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L)
- val scanResult = ScanResult(mock(), true, null, null,
+ val scanResult = ScanResult(mock(), null, null,
false, 0, null, testInfo, null)
+ var installRequest = InstallRequest(mock(), 0, 0, UserHandle(0), scanResult)
- val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult)
+ val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(installRequest)
assertThat(allowedInfos).hasSize(1)
assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME)
@@ -327,10 +330,11 @@ class SharedLibrariesImplTest {
.setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build()
val scanRequest = ScanRequest(parsedPackage, null, null, null, null,
null, null, null, 0, 0, false, null, null)
- val scanResult = ScanResult(scanRequest, true, packageSetting, null,
+ val scanResult = ScanResult(scanRequest, packageSetting, null,
false, 0, null, null, listOf(testInfo))
+ var installRequest = InstallRequest(parsedPackage, 0, 0, UserHandle(0), scanResult)
- val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult)
+ val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(installRequest)
assertThat(allowedInfos).hasSize(1)
assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
deleted file mode 100644
index 278e04ab1a41..000000000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
+++ /dev/null
@@ -1,258 +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 android.os.UserHandle.USER_SYSTEM;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertThrows;
-
-import android.util.Log;
-
-import org.junit.Test;
-
-/**
- * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerInternalTest}
- */
-public final class UserManagerInternalTest extends UserManagerServiceOrInternalTestCase {
-
- private static final String TAG = UserManagerInternalTest.class.getSimpleName();
-
- // NOTE: most the tests below only apply to MUMD configurations, so we're not adding _mumd_
- // in the test names, but _nonMumd_ instead
-
- @Test
- public void testAssignUserToDisplay_nonMumd_defaultDisplayIgnored() {
- mUmi.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_nonMumd_otherDisplay_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertThrows(UnsupportedOperationException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_nonMumd_otherDisplay_startProfileOfcurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- startDefaultProfile();
-
- assertThrows(UnsupportedOperationException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_nonMumd_otherDisplay_stoppedProfileOfcurrentUser() {
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- stopDefaultProfile();
-
- assertThrows(UnsupportedOperationException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_defaultDisplayIgnored() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_systemUser() {
- enableUsersOnSecondaryDisplays();
-
- assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
- }
-
- @Test
- public void testAssignUserToDisplay_invalidDisplay() {
- enableUsersOnSecondaryDisplays();
-
- assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
- }
-
- @Test
- public void testAssignUserToDisplay_currentUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
-
- assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- startDefaultProfile();
-
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(PARENT_USER_ID);
- addDefaultProfileAndParent();
- stopDefaultProfile();
-
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testAssignUserToDisplay_displayAvailable() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_displayAlreadyAssigned() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mUmi.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
- .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
- + USER_ID + ".*");
- }
-
- @Test
- public void testAssignUserToDisplay_userAlreadyAssigned() {
- enableUsersOnSecondaryDisplays();
-
- mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- IllegalStateException e = assertThrows(IllegalStateException.class,
- () -> mUmi.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
- .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
- + SECONDARY_DISPLAY_ID + ".*");
-
- assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
-
- mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
-
- mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
-
- mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
-
- Log.v(TAG, "Exception: " + e);
- assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
- }
-
- @Test
- public void testUnassignUserFromDisplay_nonMumd_ignored() {
- mockCurrentUser(USER_ID);
-
- mUmi.unassignUserFromDisplay(USER_SYSTEM);
- mUmi.unassignUserFromDisplay(USER_ID);
- mUmi.unassignUserFromDisplay(OTHER_USER_ID);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Test
- public void testUnassignUserFromDisplay() {
- testAssignUserToDisplay_displayAvailable();
-
- mUmi.unassignUserFromDisplay(USER_ID);
-
- assertNoUserAssignedToDisplay();
- }
-
- @Override
- protected boolean isUserVisible(int userId) {
- return mUmi.isUserVisible(userId);
- }
-
- @Override
- protected boolean isUserVisibleOnDisplay(int userId, int displayId) {
- return mUmi.isUserVisible(userId, displayId);
- }
-
- @Override
- protected int getDisplayAssignedToUser(int userId) {
- return mUmi.getDisplayAssignedToUser(userId);
- }
-
- @Override
- protected int getUserAssignedToDisplay(int displayId) {
- return mUmi.getUserAssignedToDisplay(displayId);
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
index 90a5fa0d9f38..6c85b7ab4985 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
@@ -15,10 +15,6 @@
*/
package com.android.server.pm;
-import static android.os.UserHandle.USER_NULL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,7 +30,6 @@ import android.content.pm.UserInfo;
import android.os.UserManager;
import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import androidx.test.annotation.UiThreadTest;
@@ -46,12 +41,8 @@ import com.android.server.pm.UserManagerService.UserData;
import org.junit.After;
import org.junit.Before;
-import org.junit.Test;
import org.mockito.Mock;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
/**
* Base class for {@link UserManagerInternalTest} and {@link UserManagerInternalTest}.
*
@@ -59,9 +50,13 @@ import java.util.Map;
* "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
- * provices the infra to make it easier to test both (which in turn would make it easier / safer to
+ * 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();
@@ -90,31 +85,12 @@ abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestC
*/
protected static final int PROFILE_USER_ID = 643;
- /**
- * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
- */
- protected static final int SECONDARY_DISPLAY_ID = 42;
-
- /**
- * Id of another secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
- */
- protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
-
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
private final SparseArray<UserData> mUsers = new SparseArray<>();
- // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
- // details into the unit test, but it's fine for now - in the long term, this logic should be
- // refactored into a proper UserDisplayAssignment class.
- private final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
-
private Context mSpiedContext;
- private UserManagerService mStandardUms;
- private UserManagerService mMumdUms;
- private UserManagerInternal mStandardUmi;
- private UserManagerInternal mMumdUmi;
private @Mock PackageManagerService mMockPms;
private @Mock UserDataPreparer mMockUserDataPreparer;
@@ -122,17 +98,11 @@ abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestC
/**
* Reference to the {@link UserManagerService} being tested.
- *
- * <p>By default, such service doesn't support {@code MUMD} (Multiple Users on Multiple
- * Displays), but that can be changed by calling {@link #enableUsersOnSecondaryDisplays()}.
*/
protected UserManagerService mUms;
/**
* Reference to the {@link UserManagerInternal} being tested.
- *
- * <p>By default, such service doesn't support {@code MUMD} (Multiple Users on Multiple
- * Displays), but that can be changed by calling {@link #enableUsersOnSecondaryDisplays()}.
*/
protected UserManagerInternal mUmi;
@@ -151,32 +121,12 @@ abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestC
// Called when WatchedUserStates is constructed
doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
- // Need to set both UserManagerService instances here, as they need to be run in the
- // UiThread
-
- // mMumdUms / mMumdUmi
- mockIsUsersOnSecondaryDisplaysEnabled(/* usersOnSecondaryDisplaysEnabled= */ true);
- mMumdUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
- mPackagesLock, mRealContext.getDataDir(), mUsers, mUsersOnSecondaryDisplays);
- assertWithMessage("UserManagerService.isUsersOnSecondaryDisplaysEnabled()")
- .that(mMumdUms.isUsersOnSecondaryDisplaysEnabled())
- .isTrue();
- mMumdUmi = LocalServices.getService(UserManagerInternal.class);
- assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mMumdUmi)
- .isNotNull();
- resetUserManagerInternal();
-
- // mStandardUms / mStandardUmi
- mockIsUsersOnSecondaryDisplaysEnabled(/* usersOnSecondaryDisplaysEnabled= */ false);
- mStandardUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
- mPackagesLock, mRealContext.getDataDir(), mUsers, mUsersOnSecondaryDisplays);
- assertWithMessage("UserManagerService.isUsersOnSecondaryDisplaysEnabled()")
- .that(mStandardUms.isUsersOnSecondaryDisplaysEnabled())
- .isFalse();
- mStandardUmi = LocalServices.getService(UserManagerInternal.class);
- assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mStandardUmi)
+ // 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();
- setServiceFixtures(/*usersOnSecondaryDisplaysEnabled= */ false);
}
@After
@@ -185,373 +135,10 @@ abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestC
LocalServices.removeServiceForTest(UserManagerInternal.class);
}
- //////////////////////////////////////////////////////////////////////////////////////////////
- // Methods whose UMS implementation calls UMI or vice-versa - they're tested in this class, //
- // but the subclass must provide the proper implementation //
- //////////////////////////////////////////////////////////////////////////////////////////////
-
- protected abstract boolean isUserVisible(int userId);
- protected abstract boolean isUserVisibleOnDisplay(int userId, int displayId);
- protected abstract int getDisplayAssignedToUser(int userId);
- protected abstract int getUserAssignedToDisplay(int displayId);
-
- /////////////////////////////////
- // Tests for the above methods //
- /////////////////////////////////
-
- @Test
- public void testIsUserVisible_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_NULL).that(isUserVisible(USER_NULL)).isFalse();
- }
-
- @Test
- public void testIsUserVisible_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisible_nonCurrentUser() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisible_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID).that(isUserVisible(PROFILE_USER_ID))
- .isTrue();
- }
-
- @Test
- public void testIsUserVisible_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID).that(isUserVisible(PROFILE_USER_ID))
- .isFalse();
- }
-
- @Test
- public void testIsUserVisible_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isTrue();
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // isUserVisible() for bg users relies only on the user / display assignments
-
- @Test
- public void testIsUserVisibleOnDisplay_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_NULL, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_NULL, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, INVALID_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_ID, INVALID_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_currentUserUnassignedSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_currentUserSecondaryDisplayAssignedToAnotherUser() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
- startDefaultProfile();
- mockCurrentUser(PARENT_USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
- stopDefaultProfile();
- mockCurrentUser(PARENT_USER_ID);
- assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
- }
-
- @Test
- public void testIsUserVisibleOnDisplay_mumd_bgUserOnAnotherSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
- .that(isUserVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse();
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // isUserVisibleOnDisplay() for bg users relies only on the user / display assignments
-
- @Test
- public void testGetDisplayAssignedToUser_invalidUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
- .that(getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_currentUser() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_nonCurrentUser() {
- mockCurrentUser(OTHER_USER_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- startDefaultProfile();
- setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
- .that(getDisplayAssignedToUser(PROFILE_USER_ID)).isEqualTo(DEFAULT_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
- addDefaultProfileAndParent();
- mockCurrentUser(PARENT_USER_ID);
- stopDefaultProfile();
-
- assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
- .that(getDisplayAssignedToUser(PROFILE_USER_ID)).isEqualTo(INVALID_DISPLAY);
- }
-
- @Test
- public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
- .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(SECONDARY_DISPLAY_ID);
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // getDisplayAssignedToUser() for bg users relies only on the user / display assignments
-
- @Test
- public void testGetUserAssignedToDisplay_invalidDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY)
- .that(getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_defaultDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY)
- .that(getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_secondaryDisplay() {
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_mumd_bgUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(OTHER_USER_ID);
- assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- @Test
- public void testGetUserAssignedToDisplay_mumd_noUserOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- mockCurrentUser(USER_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
- // started on secondary display if its parent isn't, so we might need to remove (or refactor
- // this test) if/when the underlying logic changes
- @Test
- public void testGetUserAssignedToDisplay_mumd_profileOnSecondaryDisplay() {
- enableUsersOnSecondaryDisplays();
- addDefaultProfileAndParent();
- mockCurrentUser(USER_ID);
- assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
- assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
- .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
- }
-
- // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
- // getUserAssignedToDisplay() for bg users relies only on the user / display assignments
-
-
///////////////////////////////////////////
// Helper methods exposed to sub-classes //
///////////////////////////////////////////
- /**
- * Change test fixtures to use a version that supports {@code MUMD} (Multiple Users on Multiple
- * Displays).
- */
- protected final void enableUsersOnSecondaryDisplays() {
- setServiceFixtures(/* usersOnSecondaryDisplaysEnabled= */ true);
- }
-
protected final void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
@@ -597,65 +184,19 @@ abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestC
setUserState(userId, UserState.STATE_STOPPING);
}
- // NOTE: should only called by tests that indirectly needs to check user assignments (like
- // isUserVisible), not by tests for the user assignment methods per se.
- protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
- mUsersOnSecondaryDisplays.put(userId, displayId);
- }
-
- protected final void assertNoUserAssignedToDisplay() {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
- .isEmpty();
- }
-
- protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
- assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
- .containsExactly(userId, displayId);
+ protected final void setUserState(@UserIdInt int userId, int userState) {
+ mUmi.setUserState(userId, userState);
}
///////////////////
// Private infra //
///////////////////
- private void setServiceFixtures(boolean usersOnSecondaryDisplaysEnabled) {
- Log.d(TAG, "Setting fixtures for usersOnSecondaryDisplaysEnabled="
- + usersOnSecondaryDisplaysEnabled);
- if (usersOnSecondaryDisplaysEnabled) {
- mUms = mMumdUms;
- mUmi = mMumdUmi;
- } else {
- mUms = mStandardUms;
- mUmi = mStandardUmi;
- }
- }
-
- private void mockIsUsersOnSecondaryDisplaysEnabled(boolean enabled) {
- Log.d(TAG, "Mocking UserManager.isUsersOnSecondaryDisplaysEnabled() to return " + enabled);
- doReturn(enabled).when(() -> UserManager.isUsersOnSecondaryDisplaysEnabled());
- }
-
private void addUserData(TestUserData userData) {
Log.d(TAG, "Adding " + userData);
mUsers.put(userData.info.id, userData);
}
- private void setUserState(@UserIdInt int userId, int userState) {
- mUmi.setUserState(userId, userState);
- }
-
- private void removeUserState(@UserIdInt int userId) {
- mUmi.removeUserState(userId);
- }
-
- private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
- int size = mUsersOnSecondaryDisplays.size();
- Map<Integer, Integer> map = new LinkedHashMap<>(size);
- for (int i = 0; i < size; i++) {
- map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
- }
- return map;
- }
-
private static final class TestUserData extends UserData {
@SuppressWarnings("deprecation")
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 8b5921cd45bd..b5ffe5f24338 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -123,24 +123,4 @@ public final class UserManagerServiceTest extends UserManagerServiceOrInternalTe
assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
.that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
}
-
- @Override
- protected boolean isUserVisible(int userId) {
- return mUms.isUserVisibleUnchecked(userId);
- }
-
- @Override
- protected boolean isUserVisibleOnDisplay(int userId, int displayId) {
- return mUms.isUserVisibleOnDisplay(userId, displayId);
- }
-
- @Override
- protected int getDisplayAssignedToUser(int userId) {
- return mUms.getDisplayAssignedToUser(userId);
- }
-
- @Override
- protected int getUserAssignedToDisplay(int displayId) {
- return mUms.getUserAssignedToDisplay(displayId);
- }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
new file mode 100644
index 000000000000..21f541f54790
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -0,0 +1,303 @@
+/*
+ * 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 android.os.UserHandle.USER_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.util.Log;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link UserVisibilityMediator} tests for devices that support concurrent Multiple
+ * Users on Multiple Displays (A.K.A {@code MUMD}).
+ *
+ * <p>Run as
+ * {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorMUMDTest}
+ */
+public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
+
+ private static final String TAG = UserVisibilityMediatorMUMDTest.class.getSimpleName();
+
+ public UserVisibilityMediatorMUMDTest() {
+ super(/* usersOnSecondaryDisplaysEnabled= */ true);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_systemUser() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_invalidDisplay() {
+ assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ stopDefaultProfile();
+
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testAssignUserToDisplay_displayAvailable() {
+ mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_displayAlreadyAssigned() {
+ mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> mMediator.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
+ .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
+ + USER_ID + ".*");
+ }
+
+ @Test
+ public void testAssignUserToDisplay_userAlreadyAssigned() {
+ mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ IllegalStateException e = assertThrows(IllegalStateException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
+ .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
+ + SECONDARY_DISPLAY_ID + ".*");
+
+ assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
+ addDefaultProfileAndParent();
+
+ mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
+ addDefaultProfileAndParent();
+
+ mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
+ addDefaultProfileAndParent();
+
+ mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
+
+ Log.v(TAG, "Exception: " + e);
+ assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testUnassignUserFromDisplay() {
+ testAssignUserToDisplay_displayAvailable();
+
+ mMediator.unassignUserFromDisplay(USER_ID);
+
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public void testIsUserVisible_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_ID)
+ .that(mMediator.isUserVisible(USER_ID)).isTrue();
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // isUserVisible() for bg users relies only on the user / display assignments
+
+ @Test
+ public void testIsUserVisibleOnDisplay_currentUserUnassignedSecondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplayAssignedToAnotherUser() {
+ mockCurrentUser(USER_ID);
+ assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+ mockCurrentUser(PARENT_USER_ID);
+ assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
+ addDefaultProfileAndParent();
+ stopDefaultProfile();
+ mockCurrentUser(PARENT_USER_ID);
+ assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+ mockCurrentUser(PARENT_USER_ID);
+
+ // TODO(b/244644281): change it to isFalse() once isUserVisible() is fixed (see note there)
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_bgUserOnAnotherSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // the tests for isUserVisible(userId, display) for non-current users relies on the explicit
+ // user / display assignments
+ // TODO(b/244644281): add such tests if the logic change
+
+ @Test
+ public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(USER_ID))
+ .isEqualTo(SECONDARY_DISPLAY_ID);
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // getDisplayAssignedToUser() for bg users relies only on the user / display assignments
+
+ @Test
+ public void testGetUserAssignedToDisplay_bgUserOnSecondaryDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+ assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetUserAssignedToDisplay_noUserOnSecondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+ }
+
+ // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
+ // started on secondary display if its parent isn't, so we might need to remove (or refactor
+ // this test) if/when the underlying logic changes
+ @Test
+ public void testGetUserAssignedToDisplay_profileOnSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(USER_ID);
+ assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+ }
+
+ // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+ // getUserAssignedToDisplay() for bg users relies only on the user / display assignments
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
new file mode 100644
index 000000000000..7ae811793949
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.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 com.android.server.pm;
+
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link UserVisibilityMediator} tests for devices that DO NOT support concurrent
+ * multiple users on multiple displays (A.K.A {@code SUSD} - Single User on Single Device).
+ *
+ * <p>Run as
+ * {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorSUSDTest}
+ */
+public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediatorTestCase {
+
+ public UserVisibilityMediatorSUSDTest() {
+ super(/* usersOnSecondaryDisplaysEnabled= */ false);
+ }
+
+ @Test
+ public void testAssignUserToDisplay_otherDisplay_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ startDefaultProfile();
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() {
+ mockCurrentUser(PARENT_USER_ID);
+ addDefaultProfileAndParent();
+ stopDefaultProfile();
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+ }
+
+ @Test
+ public void testUnassignUserFromDisplay_ignored() {
+ mockCurrentUser(USER_ID);
+
+ mMediator.unassignUserFromDisplay(USER_SYSTEM);
+ mMediator.unassignUserFromDisplay(USER_ID);
+ mMediator.unassignUserFromDisplay(OTHER_USER_ID);
+
+ assertNoUserAssignedToDisplay();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
new file mode 100644
index 000000000000..22e6e0dcae1c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -0,0 +1,323 @@
+/*
+ * 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 android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Base class for {@link UserVisibilityMediator} tests.
+ *
+ * <p>It contains common logics and tests for behaviors that should be invariant regardless of the
+ * device mode (for example, whether the device supports concurrent multiple users on multiple
+ * displays or not).
+ */
+abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrInternalTestCase {
+
+ /**
+ * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
+ */
+ protected static final int SECONDARY_DISPLAY_ID = 42;
+
+ /**
+ * Id of another secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
+ */
+ protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
+
+ private final boolean mUsersOnSecondaryDisplaysEnabled;
+
+ // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
+ // details into the unit test, but it's fine for now as the tests were copied "as is" - it
+ // would be better to use a geter() instead
+ protected final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
+
+ protected UserVisibilityMediator mMediator;
+
+ protected UserVisibilityMediatorTestCase(boolean usersOnSecondaryDisplaysEnabled) {
+ mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
+ }
+
+ @Before
+ public final void setMediator() {
+ mMediator = new UserVisibilityMediator(mUms, mUsersOnSecondaryDisplaysEnabled,
+ mUsersOnSecondaryDisplays);
+ }
+
+ @Test
+ public final void testAssignUserToDisplay_defaultDisplayIgnored() {
+ mMediator.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
+
+ assertNoUserAssignedToDisplay();
+ }
+
+ @Test
+ public final void testIsUserVisible_invalidUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_NULL)
+ .that(mMediator.isUserVisible(USER_NULL)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisible_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_ID)
+ .that(mMediator.isUserVisible(USER_ID)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisible_nonCurrentUser() {
+ mockCurrentUser(OTHER_USER_ID);
+
+ assertWithMessage("isUserVisible(%s)", USER_ID)
+ .that(mMediator.isUserVisible(USER_ID)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisible_startedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisible_stoppedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_invalidUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_NULL, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(USER_NULL, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, INVALID_DISPLAY)
+ .that(mMediator.isUserVisible(USER_ID, INVALID_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() {
+ mockCurrentUser(OTHER_USER_ID);
+
+ assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
+ }
+
+ @Test
+ public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+ }
+
+ @Test
+ public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+ .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+ }
+
+ @Test
+ public void testGetDisplayAssignedToUser_invalidUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
+ .that(mMediator.getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testGetDisplayAssignedToUser_currentUser() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public final void testGetDisplayAssignedToUser_nonCurrentUser() {
+ mockCurrentUser(OTHER_USER_ID);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ startDefaultProfile();
+ setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
+ .isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
+ addDefaultProfileAndParent();
+ mockCurrentUser(PARENT_USER_ID);
+ stopDefaultProfile();
+
+ assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
+ .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
+ .isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testGetUserAssignedToDisplay_invalidDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY)
+ .that(mMediator.getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public final void testGetUserAssignedToDisplay_defaultDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY)
+ .that(mMediator.getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public final void testGetUserAssignedToDisplay_secondaryDisplay() {
+ mockCurrentUser(USER_ID);
+
+ assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+ .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID))
+ .isEqualTo(USER_ID);
+ }
+
+ // NOTE: should only called by tests that indirectly needs to check user assignments (like
+ // isUserVisible), not by tests for the user assignment methods per se.
+ protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
+ mUsersOnSecondaryDisplays.put(userId, displayId);
+ }
+
+ protected final void assertNoUserAssignedToDisplay() {
+ assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+ .isEmpty();
+ }
+
+ protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
+ assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+ .containsExactly(userId, displayId);
+ }
+
+ private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
+ int size = mUsersOnSecondaryDisplays.size();
+ Map<Integer, Integer> map = new LinkedHashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
+ }
+ return map;
+ }
+}
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 0b776a3e6642..fe92a1dbdac1 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -24,6 +24,7 @@ import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -246,7 +247,7 @@ public class UserControllerTest {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
- mUserController.startUser(TEST_USER_ID, true /* foreground */);
+ mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
@@ -258,6 +259,8 @@ public class UserControllerTest {
assertFalse(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ true));
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -280,6 +283,8 @@ public class UserControllerTest {
// binder calls, but their side effects (in this case, that the user is stopped right away)
assertWithMessage("wrong binder message calls").that(mInjector.mHandler.getMessageCodes())
.containsExactly(USER_START_MSG);
+
+ verifyUserAssignedToDisplay(TEST_PRE_CREATED_USER_ID, Display.DEFAULT_DISPLAY);
}
private void startUserAssertions(
@@ -303,6 +308,7 @@ public class UserControllerTest {
assertEquals("User must be in STATE_BOOTING", UserState.STATE_BOOTING, userState.state);
assertEquals("Unexpected old user id", 0, reportMsg.arg1);
assertEquals("Unexpected new user id", TEST_USER_ID, reportMsg.arg2);
+ verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
}
@Test
@@ -313,6 +319,8 @@ public class UserControllerTest {
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
verify(mInjector.getWindowManager()).setSwitchingUser(false);
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -395,6 +403,7 @@ public class UserControllerTest {
verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
+ verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
}
@Test
@@ -403,7 +412,7 @@ public class UserControllerTest {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
// Start user -- this will update state of mUserController
- mUserController.startUser(TEST_USER_ID, true);
+ mUserController.startUser(TEST_USER_ID, /* foreground=*/ true);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -415,6 +424,7 @@ public class UserControllerTest {
verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
+ verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
}
@Test
@@ -423,7 +433,7 @@ public class UserControllerTest {
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
// Start user -- this will update state of mUserController
- mUserController.startUser(TEST_USER_ID, true);
+ mUserController.startUser(TEST_USER_ID, /* foreground=*/ true);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
assertNotNull(reportMsg);
UserState userState = (UserState) reportMsg.obj;
@@ -521,6 +531,7 @@ public class UserControllerTest {
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
mUserController.getRunningUsersLU());
+ verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
}
/**
@@ -530,7 +541,7 @@ public class UserControllerTest {
*/
@Test
public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
- throws InterruptedException, RemoteException {
+ throws Exception {
mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
@@ -645,6 +656,8 @@ public class UserControllerTest {
setUpUser(TEST_USER_ID1, 0);
assertThrows(IllegalArgumentException.class,
() -> mUserController.startProfile(TEST_USER_ID1));
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -660,6 +673,8 @@ public class UserControllerTest {
setUpUser(TEST_USER_ID1, UserInfo.FLAG_PROFILE | UserInfo.FLAG_DISABLED, /* preCreated= */
false, UserManager.USER_TYPE_PROFILE_MANAGED);
assertThat(mUserController.startProfile(TEST_USER_ID1)).isFalse();
+
+ verifyUserNeverAssignedToDisplay();
}
@Test
@@ -949,6 +964,10 @@ public class UserControllerTest {
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
}
+ private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
+ verify(mInjector).onUserStarting(userId, visible);
+ }
+
// Should be public to allow mocking
private static class TestInjector extends UserController.Injector {
public final TestHandler mHandler;
@@ -1084,6 +1103,11 @@ public class UserControllerTest {
protected LockPatternUtils getLockPatternUtils() {
return mLockPatternUtilsMock;
}
+
+ @Override
+ void onUserStarting(@UserIdInt int userId, boolean visible) {
+ Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
+ }
}
private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 68c9ce4a9f86..0cff4f14bf23 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -178,6 +178,23 @@ public class ALSProbeTest {
}
@Test
+ public void testWatchDogCompletesAwait() {
+ mProbe.enable();
+
+ AtomicInteger lux = new AtomicInteger(-9);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ verify(mSensorManager).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ moveTimeBy(TIMEOUT_MS);
+
+ assertThat(lux.get()).isEqualTo(-1);
+ verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+ verifyNoMoreInteractions(mSensorManager);
+ }
+
+ @Test
public void testNextLuxWhenAlreadyEnabledAndNotAvailable() {
testNextLuxWhenAlreadyEnabled(false /* dataIsAvailable */);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 5012335b533f..21c9c753a062 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -61,7 +61,7 @@ public class SensorOverlaysTest {
@Test
public void noopWhenBothNull() {
- final SensorOverlays useless = new SensorOverlays(null, null);
+ final SensorOverlays useless = new SensorOverlays(null, null, null);
useless.show(SENSOR_ID, 2, null);
useless.hide(SENSOR_ID);
}
@@ -69,12 +69,12 @@ public class SensorOverlaysTest {
@Test
public void testProvidesUdfps() {
final List<IUdfpsOverlayController> udfps = new ArrayList<>();
- SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
+ SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController, null);
sensorOverlays.ifUdfps(udfps::add);
assertThat(udfps).isEmpty();
- sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
+ sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController, null);
sensorOverlays.ifUdfps(udfps::add);
assertThat(udfps).containsExactly(mUdfpsOverlayController);
}
@@ -96,7 +96,7 @@ public class SensorOverlaysTest {
private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
throws Exception {
- final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
@@ -126,7 +126,7 @@ public class SensorOverlaysTest {
private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
throws Exception {
- final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+ final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
sensorOverlays.hide(SENSOR_ID);
if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 12b8264fc20c..41f743367aeb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -39,6 +39,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -63,6 +64,8 @@ public class FaceProviderTest {
private IFace mDaemon;
@Mock
private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricStateCallback mBiometricStateCallback;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -91,8 +94,8 @@ public class FaceProviderTest {
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mSensorProps, TAG,
- mLockoutResetDispatcher, mBiometricContext);
+ mFaceProvider = new TestableFaceProvider(mDaemon, mContext, mBiometricStateCallback,
+ mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext);
}
@SuppressWarnings("rawtypes")
@@ -140,11 +143,13 @@ public class FaceProviderTest {
TestableFaceProvider(@NonNull IFace daemon,
@NonNull Context context,
+ @NonNull BiometricStateCallback biometricStateCallback,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext) {
- super(context, props, halInstanceName, lockoutResetDispatcher, biometricContext);
+ super(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
+ biometricContext);
mDaemon = daemon;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index 116d2d5a66a0..a2cade7ad797 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -43,6 +43,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
+import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import org.junit.Before;
@@ -73,6 +74,8 @@ public class Face10Test {
private BiometricScheduler mScheduler;
@Mock
private BiometricContext mBiometricContext;
+ @Mock
+ private BiometricStateCallback mBiometricStateCallback;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -103,8 +106,8 @@ public class Face10Test {
resetLockoutRequiresChallenge);
Face10.sSystemClock = Clock.fixed(Instant.ofEpochMilli(100), ZoneId.of("PST"));
- mFace10 = new Face10(mContext, sensorProps, mLockoutResetDispatcher, mHandler, mScheduler,
- mBiometricContext);
+ mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
+ mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
mBinder = new Binder();
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 1b5db0a35449..675f0e357aa8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -645,7 +645,7 @@ public class FingerprintAuthenticationClientTest {
9 /* sensorId */, mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, mLockoutCache,
- mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
+ mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
mSensorProps,
new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 93cbef19aca9..4579fc15f661 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -118,6 +118,6 @@ public class FingerprintDetectClientTest {
return new FingerprintDetectClient(mContext, () -> aidl, mToken,
6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
"a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
- mUdfpsOverlayController, true /* isStrongBiometric */);
+ mUdfpsOverlayController, null, true /* isStrongBiometric */);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 837b55397416..38b06c476174 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,7 +65,6 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
-import java.util.ArrayList;
import java.util.function.Consumer;
@Presubmit
@@ -292,6 +290,6 @@ public class FingerprintEnrollClientTest {
mClientMonitorCallbackConverter, 0 /* userId */,
HAT, "owner", mBiometricUtils, 8 /* sensorId */,
mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
- mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+ mSideFpsController, null, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
}
}
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 02bbe658f9b2..5fda3d6b36ab 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
@@ -891,4 +891,34 @@ public class VirtualDeviceManagerServiceTest {
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
+
+ @Test
+ public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
+ ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
+ mDeviceImpl.onVirtualDisplayCreatedLocked(
+ mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+
+ gwpc.onRunningAppsChanged(uids);
+ mDeviceImpl.onRunningAppsChanged(uids);
+
+ assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1);
+ verify(mRunningAppsChangedCallback).accept(new ArraySet<>(Arrays.asList(UID_1, UID_2)));
+ }
+
+ @Test
+ public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
+ ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
+ mDeviceImpl.onVirtualDisplayCreatedLocked(
+ mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+ GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+ DISPLAY_ID);
+ mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
+
+ // This call should not throw any exceptions.
+ gwpc.onRunningAppsChanged(uids);
+
+ assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 7a2a5838134c..54baf18da92a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -590,11 +590,15 @@ public class HdmiCecLocalDeviceTvTest {
@Test
public void handleReportAudioStatus_SamOnArcOff_setStreamVolumeNotCalled() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
+ HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED);
// Emulate Audio device on port 0x1000 (does not support ARC)
mNativeWrapper.setPortConnectionStatus(1, true);
HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
HdmiCecFeatureAction systemAudioAutoInitiationAction =
new SystemAudioAutoInitiationAction(mHdmiCecLocalDeviceTv, ADDR_AUDIO_SYSTEM);
@@ -821,6 +825,10 @@ public class HdmiCecLocalDeviceTvTest {
@Test
public void receiveSetAudioVolumeLevel_samActivated_respondsFeatureAbort_noVolumeChange() {
+ mHdmiControlService.getHdmiCecConfig().setIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
+ HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED);
+
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildSetSystemAudioMode(
ADDR_AUDIO_SYSTEM, ADDR_TV, true));
mTestLooper.dispatchAll();
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index c68db3460dac..6590a2b500e4 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -36,6 +36,7 @@ import androidx.test.InstrumentationRegistry
import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
import com.android.server.input.BatteryController.UEventManager
import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
+import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat
@@ -528,10 +529,109 @@ class BatteryControllerTests {
matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
// The battery is no longer present after the timeout expires.
- testLooper.moveTimeForward(BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
+ testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
testLooper.dispatchNext()
listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2))
assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
isInvalidBatteryState(USI_DEVICE_ID))
}
+
+ @Test
+ fun testStylusPresenceExtendsValidUsiBatteryState() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // There is a UEvent signaling a battery change. The battery state is now valid.
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+ // Stylus presence is detected before the validity timeout expires.
+ testLooper.moveTimeForward(100)
+ testLooper.dispatchAll()
+ batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+ // Ensure that timeout was extended, and the battery state is now valid for longer.
+ testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 100)
+ testLooper.dispatchAll()
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+ // Ensure the validity period expires after the expected amount of time.
+ testLooper.moveTimeForward(100)
+ testLooper.dispatchNext()
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+ }
+
+ @Test
+ fun testStylusPresenceMakesUsiBatteryStateValid() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // The USI battery state is initially invalid.
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+
+ // A stylus presence is detected. This validates the battery state.
+ batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+ }
+
+ @Test
+ fun testStylusPresenceDoesNotMakeUsiBatteryStateValidAtBoot() {
+ `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+ // At boot, the USI device always reports a capacity value of 0.
+ `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_UNKNOWN)
+ `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(0)
+
+ addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+ testLooper.dispatchNext()
+ val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+ verify(uEventManager)
+ .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+ // The USI battery state is initially invalid.
+ val listener = createMockListener()
+ batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+ listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+
+ // Since the capacity reported is 0, stylus presence does not validate the battery state.
+ batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ isInvalidBatteryState(USI_DEVICE_ID))
+
+ // However, if a UEvent reports a battery capacity of 0, the battery state is now valid.
+ uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+ listener.verifyNotified(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f)
+ assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+ matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index dbcd38c35958..dc9f90737713 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -229,6 +229,29 @@ public class LocaleManagerServiceTest {
}
}
+ @Test(expected = SecurityException.class)
+ public void testGetApplicationLocales_currentImeQueryNonForegroundAppLocales_fails()
+ throws Exception {
+ doReturn(DEFAULT_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(anyString(), any(), anyInt());
+ doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+ .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
+ String imPkgName = getCurrentInputMethodPackageName();
+ doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+ .getPackageUidAsUser(eq(imPkgName), any(), anyInt());
+ doReturn(false).when(mMockActivityManager).isAppForeground(anyInt());
+ setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+ try {
+ mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+ fail("Expected SecurityException");
+ } finally {
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES),
+ anyString());
+ }
+ }
+
@Test
public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
throws Exception {
@@ -307,7 +330,7 @@ public class LocaleManagerServiceTest {
}
@Test
- public void testGetApplicationLocales_callerIsCurrentInputMethod_returnsLocales()
+ public void testGetApplicationLocales_currentImeQueryForegroundAppLocales_returnsLocales()
throws Exception {
doReturn(DEFAULT_UID).when(mMockPackageManager)
.getPackageUidAsUser(anyString(), any(), anyInt());
@@ -316,6 +339,7 @@ public class LocaleManagerServiceTest {
String imPkgName = getCurrentInputMethodPackageName();
doReturn(Binder.getCallingUid()).when(mMockPackageManager)
.getPackageUidAsUser(eq(imPkgName), any(), anyInt());
+ doReturn(true).when(mMockActivityManager).isAppForeground(anyInt());
LocaleList locales =
mLocaleManagerService.getApplicationLocales(
diff --git a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
index 4d03749d2759..48d6d9037e95 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ScanTests.java
@@ -568,7 +568,6 @@ public class ScanTests {
private static void assertBasicPackageScanResult(
ScanResult scanResult, String packageName, boolean isInstant) {
- assertThat(scanResult.mSuccess, is(true));
final PackageSetting pkgSetting = scanResult.mPkgSetting;
assertBasicPackageSetting(scanResult, packageName, isInstant, pkgSetting);
diff --git a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
index 0b27f8734f23..4762696d7c61 100644
--- a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
@@ -125,7 +125,7 @@ public class EventLoggerTest {
@Test
public void testThatLoggingWorksAsExpected() {
for (EventLogger.Event event: mEventsToInsert) {
- mEventLogger.log(event);
+ mEventLogger.enqueue(event);
}
mEventLogger.dump(mTestPrintWriter);
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
index a917c57446a9..6ef5b0ecb2a0 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
@@ -51,6 +51,7 @@ import com.android.server.UiServiceTestCase;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -87,6 +88,7 @@ public class SliceManagerServiceTest extends UiServiceTestCase {
LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
}
+ @Ignore("b/253871109")
@Test
public void testAddPinCreatesPinned() throws RemoteException {
grantSlicePermission();
@@ -97,6 +99,7 @@ public class SliceManagerServiceTest extends UiServiceTestCase {
verify(mService, times(1)).createPinnedSlice(eq(maybeAddUserId(TEST_URI, 0)), anyString());
}
+ @Ignore("b/253871109")
@Test
public void testRemovePinDestroysPinned() throws RemoteException {
grantSlicePermission();
@@ -109,6 +112,7 @@ public class SliceManagerServiceTest extends UiServiceTestCase {
verify(mCreatedSliceState, never()).destroy();
}
+ @Ignore("b/253871109")
@Test
public void testCheckAutoGrantPermissions() throws RemoteException {
String[] testPerms = new String[] {
@@ -128,12 +132,14 @@ public class SliceManagerServiceTest extends UiServiceTestCase {
verify(mContextSpy).checkPermission(eq("perm2"), eq(Process.myPid()), eq(Process.myUid()));
}
+ @Ignore("b/253871109")
@Test(expected = IllegalStateException.class)
public void testNoPinThrow() throws Exception {
grantSlicePermission();
mService.getPinnedSpecs(TEST_URI, "pkg");
}
+ @Ignore("b/253871109")
@Test
public void testGetPinnedSpecs() throws Exception {
grantSlicePermission();
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 8a18912b8b68..4ec9762e8399 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -49,6 +49,7 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -146,7 +147,6 @@ import android.view.IWindowManager;
import android.view.IWindowSession;
import android.view.InsetsSource;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
@@ -2002,7 +2002,7 @@ public class ActivityRecordTests extends WindowTestsBase {
doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
- any() /* requestedVisibilities */, any() /* outInputChannel */,
+ anyInt() /* requestedVisibleTypes */, any() /* outInputChannel */,
any() /* outInsetsState */, any() /* outActiveControls */,
any() /* outAttachedFrame */, any() /* outSizeCompatScale */);
mAtm.mWindowManager.mStartingSurfaceController
@@ -3233,9 +3233,7 @@ public class ActivityRecordTests extends WindowTestsBase {
app2.mActivityRecord.commitVisibility(false, false);
// app1 requests IME visible.
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_IME, true);
- app1.setRequestedVisibilities(requestedVisibilities);
+ app1.setRequestedVisibleTypes(ime(), ime());
mDisplayContent.getInsetsStateController().onInsetsModified(app1);
// Verify app1's IME insets is visible and app2's IME insets frozen flag set.
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 e69418ba908e..37ab9a00737f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -34,13 +34,14 @@ import static android.view.Display.FLAG_PRIVATE;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -130,7 +131,6 @@ import android.view.IDisplayChangeWindowController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -1399,10 +1399,7 @@ public class DisplayContentTests extends WindowTestsBase {
win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
win.getAttrs().insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- win.setRequestedVisibilities(requestedVisibilities);
+ win.setRequestedVisibleTypes(0, navigationBars() | statusBars());
win.mActivityRecord.mTargetSdk = P;
performLayout(dc);
@@ -2486,7 +2483,7 @@ public class DisplayContentTests extends WindowTestsBase {
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
spyOn(imeAppTarget);
- doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME);
+ doReturn(true).when(imeAppTarget).isRequestedVisible(ime());
assertEquals(imeChildWindow, mDisplayContent.findFocusedWindow());
// Verify imeChildWindow doesn't be focused window if the next IME target does not
@@ -2511,7 +2508,7 @@ public class DisplayContentTests extends WindowTestsBase {
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget");
mDisplayContent.setImeLayeringTarget(imeAppTarget);
spyOn(imeAppTarget);
- doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME);
+ doReturn(true).when(imeAppTarget).isRequestedVisible(ime());
assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
// Verify imeMenuDialog doesn't be focused window if the next IME target is closing.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index a980765711d1..52af8adcf9f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -371,7 +371,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
final InsetsControlTarget controlTarget = mock(InsetsControlTarget.class);
when(provider.getControlTarget()).thenReturn(controlTarget);
when(windowState.getControllableInsetProvider()).thenReturn(provider);
- when(controlTarget.getRequestedVisibility(anyInt())).thenReturn(true);
+ when(controlTarget.isRequestedVisible(anyInt())).thenReturn(true);
displayPolicy.setCanSystemBarsBeShownByUser(false);
displayPolicy.requestTransientBars(windowState, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index f2bc47dfd6e9..fd2a1d1c352f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,11 +19,15 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
@@ -51,7 +55,7 @@ import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
import androidx.test.filters.SmallTest;
@@ -74,7 +78,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
@Test
public void testControlsForDispatch_regular() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -86,7 +90,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
@Test
public void testControlsForDispatch_multiWindowTaskVisible() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final WindowState win = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
@@ -99,7 +103,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
@Test
public void testControlsForDispatch_freeformTaskVisible() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM,
ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app");
@@ -112,7 +116,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
@Test
public void testControlsForDispatch_forceStatusBarVisible() {
addStatusBar().mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -126,7 +130,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -139,7 +143,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
public void testControlsForDispatch_statusBarForceShowNavigation_butFocusedAnyways() {
WindowState notifShade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
notifShade.mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
mDisplayContent.getInsetsPolicy().updateBarControlTarget(notifShade);
InsetsSourceControl[] controls
@@ -155,7 +159,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -166,13 +170,11 @@ public class InsetsPolicyTest extends WindowTestsBase {
@Test
public void testControlsForDispatch_topAppHidesStatusBar() {
addStatusBar();
- addWindow(TYPE_NAVIGATION_BAR, "navBar");
+ addNavigationBar();
// Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar.
final WindowState fullscreenApp = addWindow(TYPE_APPLICATION, "fullscreenApp");
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- fullscreenApp.setRequestedVisibilities(requestedVisibilities);
+ fullscreenApp.setRequestedVisibleTypes(0, WindowInsets.Type.statusBars());
// Add a non-fullscreen dialog window.
final WindowState dialog = addWindow(TYPE_APPLICATION, "dialog");
@@ -205,9 +207,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
// Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't.
final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp");
- final InsetsVisibilities newRequestedVisibilities = new InsetsVisibilities();
- newRequestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
- newFocusedFullscreenApp.setRequestedVisibilities(newRequestedVisibilities);
+ newFocusedFullscreenApp.setRequestedVisibleTypes(
+ WindowInsets.Type.statusBars(), WindowInsets.Type.statusBars());
// Make sure status bar is hidden by previous insets state.
mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp);
@@ -261,17 +262,15 @@ public class InsetsPolicyTest extends WindowTestsBase {
final WindowState statusBar = addStatusBar();
statusBar.setHasSurface(true);
statusBar.getControllableInsetProvider().setServerVisible(true);
- final WindowState navBar = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar");
+ final WindowState navBar = addNavigationBar();
navBar.setHasSurface(true);
navBar.getControllableInsetProvider().setServerVisible(true);
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
// Make both system bars invisible.
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
- mAppWindow.setRequestedVisibilities(requestedVisibilities);
+ mAppWindow.setRequestedVisibleTypes(
+ 0, navigationBars() | statusBars());
policy.updateBarControlTarget(mAppWindow);
waitUntilWindowAnimatorIdle();
assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
@@ -301,8 +300,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
@Test
public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() {
addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
- addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().setServerVisible(true);
+ addNavigationBar().getControllableInsetProvider().setServerVisible(true);
final InsetsPolicy policy = spy(mDisplayContent.getInsetsPolicy());
doNothing().when(policy).startAnimation(anyBoolean(), any());
@@ -331,8 +329,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() {
final InsetsSource statusBarSource =
addStatusBar().getControllableInsetProvider().getSource();
- final InsetsSource navBarSource = addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().getSource();
+ final InsetsSource navBarSource =
+ addNavigationBar().getControllableInsetProvider().getSource();
statusBarSource.setVisible(false);
navBarSource.setVisible(false);
mAppWindow.mAboveInsetsState.addSource(navBarSource);
@@ -364,10 +362,8 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible());
assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
- requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, true);
- mAppWindow.setRequestedVisibilities(requestedVisibilities);
+ mAppWindow.setRequestedVisibleTypes(
+ navigationBars() | statusBars(), navigationBars() | statusBars());
policy.onInsetsModified(mAppWindow);
waitUntilWindowAnimatorIdle();
@@ -383,8 +379,7 @@ public class InsetsPolicyTest extends WindowTestsBase {
@Test
public void testShowTransientBars_abortsWhenControlTargetChanges() {
addStatusBar().getControllableInsetProvider().getSource().setVisible(false);
- addNonFocusableWindow(TYPE_NAVIGATION_BAR, "navBar")
- .getControllableInsetProvider().getSource().setVisible(false);
+ addNavigationBar().getControllableInsetProvider().getSource().setVisible(false);
final WindowState app = addWindow(TYPE_APPLICATION, "app");
final WindowState app2 = addWindow(TYPE_APPLICATION, "app");
@@ -400,9 +395,15 @@ public class InsetsPolicyTest extends WindowTestsBase {
assertFalse(policy.isTransient(ITYPE_NAVIGATION_BAR));
}
- private WindowState addNonFocusableWindow(int type, String name) {
- WindowState win = addWindow(type, name);
+ private WindowState addNavigationBar() {
+ final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+ win.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
+ new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
+ new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ };
+ mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
return win;
}
@@ -429,6 +430,10 @@ public class InsetsPolicyTest extends WindowTestsBase {
}
private InsetsSourceControl[] addWindowAndGetControlsForDispatch(WindowState win) {
+ mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
+ // Force update the focus in DisplayPolicy here. Otherwise, without server side focus
+ // update, the policy relying on windowing type will never get updated.
+ mDisplayContent.getDisplayPolicy().focusChangedLw(null, win);
mDisplayContent.getInsetsPolicy().updateBarControlTarget(win);
return mDisplayContent.getInsetsStateController().getControlsForDispatch(win);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index fe14d8e02592..c898119ea991 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -24,6 +24,7 @@ import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -48,7 +49,6 @@ import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
@@ -187,10 +187,7 @@ public class InsetsStateControllerTest extends WindowTestsBase {
getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
getController().onImeControlTargetChanged(
mDisplayContent.getImeInputTarget().getWindowState());
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_IME, true);
- mDisplayContent.getImeInputTarget().getWindowState()
- .setRequestedVisibilities(requestedVisibilities);
+ mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime());
getController().onInsetsModified(mDisplayContent.getImeInputTarget().getWindowState());
// Send our spy window (app) into the system so that we can detect the invocation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
new file mode 100644
index 000000000000..1246d1ee46e8
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationPersisterTest {
+
+ private static final long TIMEOUT = 2000L; // 2 secs
+
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+ private Context mContext;
+ private PersisterQueue mPersisterQueue;
+ private QueueState mQueueState;
+ private PersisterQueue.Listener mQueueListener;
+ private File mConfigFolder;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = getInstrumentation().getTargetContext();
+ mConfigFolder = mContext.getFilesDir();
+ mPersisterQueue = new PersisterQueue();
+ mQueueState = new QueueState();
+ mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability),
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability),
+ mConfigFolder, mPersisterQueue, mQueueState);
+ mQueueListener = queueEmpty -> mQueueState.onItemAdded();
+ mPersisterQueue.addListener(mQueueListener);
+ mLetterboxConfigurationPersister.start();
+ }
+
+ public void tearDown() throws InterruptedException {
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ mPersisterQueue.removeListener(mQueueListener);
+ stopPersisterSafe(mPersisterQueue);
+ }
+
+ @Test
+ public void test_whenStoreIsCreated_valuesAreDefaults() {
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreWritten() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
+ final PersisterQueue firstPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), firstPersisterQueue,
+ mQueueState);
+ firstPersister.start();
+ firstPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ firstPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(firstPersisterQueue);
+ stopPersisterSafe(firstPersisterQueue);
+ final PersisterQueue secondPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), secondPersisterQueue,
+ mQueueState);
+ secondPersister.start();
+ final int newPositionForHorizontalReachability =
+ secondPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ secondPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(secondPersister, secondPersisterQueue);
+ waitForCompletion(secondPersisterQueue);
+ stopPersisterSafe(secondPersisterQueue);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ private void stopPersisterSafe(PersisterQueue persisterQueue) {
+ try {
+ persisterQueue.stopPersisting();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void waitForCompletion(PersisterQueue persisterQueue) {
+ final long endTime = System.currentTimeMillis() + TIMEOUT;
+ // The queue could be empty but the last item still processing and not completed. For this
+ // reason the completion happens when there are not more items to process and the last one
+ // has completed.
+ while (System.currentTimeMillis() < endTime && (!isQueueEmpty(persisterQueue)
+ || !hasLastItemCompleted())) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) { /* Nope */}
+ }
+ }
+
+ private boolean isQueueEmpty(PersisterQueue persisterQueue) {
+ return persisterQueue.findLastItem(
+ writeQueueItem -> true, PersisterQueue.WriteQueueItem.class) != null;
+ }
+
+ private boolean hasLastItemCompleted() {
+ return mQueueState.isEmpty();
+ }
+
+ private void deleteConfiguration(LetterboxConfigurationPersister persister,
+ PersisterQueue persisterQueue) {
+ final AtomicFile fileToDelete = new AtomicFile(
+ new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+ persisterQueue.addItem(
+ new DeleteFileCommand(fileToDelete, mQueueState.andThen(
+ s -> persister.useDefaultValue())), true);
+ }
+
+ private static class DeleteFileCommand implements
+ PersisterQueue.WriteQueueItem<DeleteFileCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToDelete;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+ DeleteFileCommand(@NonNull AtomicFile fileToDelete, Consumer<String> onComplete) {
+ mFileToDelete = fileToDelete;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ mFileToDelete.delete();
+ if (mOnComplete != null) {
+ mOnComplete.accept("DeleteFileCommand");
+ }
+ }
+ }
+
+ // Contains the current length of the persister queue
+ private static class QueueState implements Consumer<String> {
+
+ // The current number of commands in the queue
+ @VisibleForTesting
+ private final AtomicInteger mCounter = new AtomicInteger(0);
+
+ @Override
+ public void accept(String s) {
+ mCounter.decrementAndGet();
+ }
+
+ void onItemAdded() {
+ mCounter.incrementAndGet();
+ }
+
+ boolean isEmpty() {
+ return mCounter.get() == 0;
+ }
+
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
new file mode 100644
index 000000000000..c927f9e449d5
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.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 com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationTest {
+
+ private LetterboxConfiguration mLetterboxConfiguration;
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = getInstrumentation().getTargetContext();
+ mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class);
+ mLetterboxConfiguration = new LetterboxConfiguration(context,
+ mLetterboxConfigurationPersister);
+ }
+
+ @Test
+ public void test_whenReadingValues_storeIsInvoked() {
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability();
+ }
+
+ @Test
+ public void test_whenSettingValues_updateConfigurationIsInvoked() {
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ anyInt());
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ anyInt());
+ }
+
+ @Test
+ public void test_whenMovedHorizontally_updatePositionAccordingly() {
+ // Starting from center
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from left
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from right
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ }
+
+ @Test
+ public void test_whenMovedVertically_updatePositionAccordingly() {
+ // Starting from center
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from top
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from bottom
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ }
+
+ private void assertForHorizontalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForHorizontalReachability(
+ expected);
+ }
+
+ private void assertForVerticalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForVerticalReachability(
+ expected);
+ }
+}
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 29a514c2a3d7..d4c9087a9a69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -60,7 +60,9 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.app.ActivityManager;
import android.content.res.Configuration;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
@@ -80,6 +82,8 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
+import com.android.internal.graphics.ColorUtils;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -1387,6 +1391,50 @@ public class TransitionTests extends WindowTestsBase {
}
@Test
+ public void testChangeSetBackgroundColor() {
+ final Transition transition = createTestTransition(TRANSIT_CHANGE);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ // Test background color for Activity and embedded TaskFragment.
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
+ final ActivityRecord nonEmbeddedActivity = createActivityRecord(task);
+ final ActivityManager.TaskDescription taskDescription =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW)
+ .build();
+ task.setTaskDescription(taskDescription);
+
+ // Start states:
+ embeddedActivity.mVisibleRequested = true;
+ nonEmbeddedActivity.mVisibleRequested = false;
+ changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
+ changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
+ // End states:
+ embeddedActivity.mVisibleRequested = false;
+ nonEmbeddedActivity.mVisibleRequested = true;
+
+ participants.add(embeddedTf);
+ participants.add(nonEmbeddedActivity);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType,
+ 0 /* flags */, targets, changes, mMockT);
+
+ // Background color should be set on both Activity and embedded TaskFragment.
+ final int expectedBackgroundColor = ColorUtils.setAlphaComponent(
+ taskDescription.getBackgroundColor(), 255);
+ assertEquals(2, info.getChanges().size());
+ assertEquals(expectedBackgroundColor, info.getChanges().get(0).getBackgroundColor());
+ assertEquals(expectedBackgroundColor, info.getChanges().get(1).getBackgroundColor());
+ }
+
+ @Test
public void testTransitionVisibleChange() {
registerTestTransitionPlayer();
final ActivityRecord app = createActivityRecord(mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index e824f3d2916d..383722a556bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -31,7 +32,6 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.InsetsSource;
-import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
@@ -207,9 +207,7 @@ public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
mProvider.updateControlForTarget(target, false /* force */);
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- target.setRequestedVisibilities(requestedVisibilities);
+ target.setRequestedVisibleTypes(0, statusBars());
mProvider.updateClientVisibility(target);
assertFalse(mSource.isVisible());
}
@@ -220,9 +218,7 @@ public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
statusBar.getFrame().set(0, 0, 500, 100);
mProvider.setWindowContainer(statusBar, null, null);
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- target.setRequestedVisibilities(requestedVisibilities);
+ target.setRequestedVisibleTypes(0, statusBars());
mProvider.updateClientVisibility(target);
assertTrue(mSource.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index 739e783e7c1b..56c59cc8eca9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -41,7 +41,6 @@ import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.WindowInsets;
import android.view.WindowLayout;
import android.view.WindowManager;
@@ -81,7 +80,7 @@ public class WindowLayoutTests {
private int mWindowingMode;
private int mRequestedWidth;
private int mRequestedHeight;
- private InsetsVisibilities mRequestedVisibilities;
+ private int mRequestedVisibleTypes;
private float mCompatScale;
@Before
@@ -98,14 +97,14 @@ public class WindowLayoutTests {
mWindowingMode = WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
mRequestedWidth = DISPLAY_WIDTH;
mRequestedHeight = DISPLAY_HEIGHT;
- mRequestedVisibilities = new InsetsVisibilities();
+ mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
mCompatScale = 1f;
mFrames.attachedFrame = null;
}
private void computeFrames() {
mWindowLayout.computeFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds,
- mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibilities,
+ mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibleTypes,
mCompatScale, mFrames);
}
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 cf24ff2e99a5..4429aef02f94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -71,10 +71,10 @@ import android.util.MergedConfiguration;
import android.view.IWindowSessionCallback;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import android.window.ScreenCapture;
@@ -338,7 +338,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
.getWindowType(eq(windowContextToken));
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
- UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(),
+ UserHandle.USER_SYSTEM, WindowInsets.Type.defaultVisible(), null, new InsetsState(),
new InsetsSourceControl[0], new Rect(), new float[1]);
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(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 163666792bc7..0139f6a5695a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -26,6 +26,7 @@ import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -92,7 +93,6 @@ import android.view.Gravity;
import android.view.InputWindowHandle;
import android.view.InsetsSource;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -430,9 +430,7 @@ public class WindowStateTests extends WindowTestsBase {
null /* imeFrameProvider */);
mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
- final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
- requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
- app.setRequestedVisibilities(requestedVisibilities);
+ app.setRequestedVisibleTypes(0, statusBars());
mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
.updateClientVisibility(app);
waitUntilHandlersIdle();
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 40326e9ad7f6..eca7cbbc5486 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -26,6 +26,8 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.os.Process.SYSTEM_UID;
+import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
+import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
@@ -92,7 +94,6 @@ import android.view.IWindow;
import android.view.InsetsFrameProvider;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
@@ -347,6 +348,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mNavBarWindow.mAttrs.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+ mNavBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
+ new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
+ new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ };
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
mNavBarWindow.mAttrs.paramsForRotation[rot] =
getNavBarLayoutParamsForRotation(rot);
@@ -400,6 +406,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
lp.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ lp.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
+ new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
+ new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+ };
return lp;
}
@@ -846,7 +857,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
@Override
public void topFocusedWindowChanged(ComponentName component,
- InsetsVisibilities requestedVisibilities) {
+ int requestedVisibleTypes) {
}
};
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 921f6e20c3ad..372fdaf08bf9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -129,7 +129,10 @@ final class HotwordDetectionConnection {
private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
// TODO: These constants need to be refined.
- private static final long VALIDATION_TIMEOUT_MILLIS = 4000;
+ // The validation timeout value is 3 seconds for onDetect of DSP trigger event.
+ private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
+ // Write the onDetect timeout metric when it takes more time than MAX_VALIDATION_TIMEOUT_MILLIS.
+ private static final long MAX_VALIDATION_TIMEOUT_MILLIS = 4000;
private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000;
private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
@@ -659,6 +662,10 @@ final class HotwordDetectionConnection {
synchronized (mLock) {
mValidatingDspTrigger = true;
mRemoteHotwordDetectionService.run(service -> {
+ // We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
+ // the callback before timeout value. In order to reduce the latency impact between
+ // server side and client side, we need to use another timeout value
+ // MAX_VALIDATION_TIMEOUT_MILLIS to monitor it.
mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
() -> {
// TODO: avoid allocate every time
@@ -673,7 +680,7 @@ final class HotwordDetectionConnection {
Slog.w(TAG, "Failed to report onError status: ", e);
}
},
- VALIDATION_TIMEOUT_MILLIS,
+ MAX_VALIDATION_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
service.detectFromDspSource(
recognitionEvent,
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 439eaa69e771..ef693b5278a0 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3942,6 +3942,10 @@ public class SubscriptionManager {
* may provide one. Or, a carrier may decide to provide the phone number via source
* {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} if neither source UICC nor IMS is available.
*
+ * <p>The availability and correctness of the phone number depends on the underlying source
+ * and the network etc. Additional verification is needed to use this number for
+ * security-related or other sensitive scenarios.
+ *
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
* @param source the source of the phone number, one of the PHONE_NUMBER_SOURCE_* constants.
@@ -4175,18 +4179,18 @@ public class SubscriptionManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
- public void setUserHandle(int subscriptionId, @Nullable UserHandle userHandle) {
+ public void setSubscriptionUserHandle(int subscriptionId, @Nullable UserHandle userHandle) {
if (!isValidSubscriptionId(subscriptionId)) {
- throw new IllegalArgumentException("[setUserHandle]: Invalid subscriptionId: "
- + subscriptionId);
+ throw new IllegalArgumentException("[setSubscriptionUserHandle]: "
+ + "Invalid subscriptionId: " + subscriptionId);
}
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- iSub.setUserHandle(userHandle, subscriptionId, mContext.getOpPackageName());
+ iSub.setSubscriptionUserHandle(userHandle, subscriptionId);
} else {
- throw new IllegalStateException("[setUserHandle]: "
+ throw new IllegalStateException("[setSubscriptionUserHandle]: "
+ "subscription service unavailable");
}
} catch (RemoteException ex) {
@@ -4211,18 +4215,18 @@ public class SubscriptionManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
- public @Nullable UserHandle getUserHandle(int subscriptionId) {
+ public @Nullable UserHandle getSubscriptionUserHandle(int subscriptionId) {
if (!isValidSubscriptionId(subscriptionId)) {
- throw new IllegalArgumentException("[getUserHandle]: Invalid subscriptionId: "
- + subscriptionId);
+ throw new IllegalArgumentException("[getSubscriptionUserHandle]: "
+ + "Invalid subscriptionId: " + subscriptionId);
}
try {
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
- return iSub.getUserHandle(subscriptionId, mContext.getOpPackageName());
+ return iSub.getSubscriptionUserHandle(subscriptionId);
} else {
- throw new IllegalStateException("[getUserHandle]: "
+ throw new IllegalStateException("[getSubscriptionUserHandle]: "
+ "subscription service unavailable");
}
} catch (RemoteException ex) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9ecebf1a28f3..ff1b1c097d68 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3323,15 +3323,14 @@ public class TelephonyManager {
case NETWORK_TYPE_TD_SCDMA:
return NETWORK_TYPE_BITMASK_TD_SCDMA;
case NETWORK_TYPE_LTE:
- return NETWORK_TYPE_BITMASK_LTE;
case NETWORK_TYPE_LTE_CA:
- return NETWORK_TYPE_BITMASK_LTE_CA;
+ return NETWORK_TYPE_BITMASK_LTE;
case NETWORK_TYPE_NR:
return NETWORK_TYPE_BITMASK_NR;
case NETWORK_TYPE_IWLAN:
return NETWORK_TYPE_BITMASK_IWLAN;
case NETWORK_TYPE_IDEN:
- return (1 << (NETWORK_TYPE_IDEN - 1));
+ return NETWORK_TYPE_BITMASK_IDEN;
default:
return NETWORK_TYPE_BITMASK_UNKNOWN;
}
@@ -13911,7 +13910,8 @@ public class TelephonyManager {
NETWORK_TYPE_BITMASK_LTE,
NETWORK_TYPE_BITMASK_LTE_CA,
NETWORK_TYPE_BITMASK_NR,
- NETWORK_TYPE_BITMASK_IWLAN
+ NETWORK_TYPE_BITMASK_IWLAN,
+ NETWORK_TYPE_BITMASK_IDEN
})
public @interface NetworkTypeBitMask {}
@@ -13971,6 +13971,10 @@ public class TelephonyManager {
*/
public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1));
/**
+ * network type bitmask indicating the support of radio tech iDen.
+ */
+ public static final long NETWORK_TYPE_BITMASK_IDEN = (1 << (NETWORK_TYPE_IDEN - 1));
+ /**
* network type bitmask indicating the support of radio tech HSPAP.
*/
public static final long NETWORK_TYPE_BITMASK_HSPAP = (1 << (NETWORK_TYPE_HSPAP -1));
@@ -13988,12 +13992,13 @@ public class TelephonyManager {
*/
public static final long NETWORK_TYPE_BITMASK_LTE = (1 << (NETWORK_TYPE_LTE -1));
/**
- * NOT USED; this bitmask is exposed accidentally, will be deprecated in U.
+ * NOT USED; this bitmask is exposed accidentally.
* If used, will be converted to {@link #NETWORK_TYPE_BITMASK_LTE}.
* network type bitmask indicating the support of radio tech LTE CA (carrier aggregation).
*
- * @see #NETWORK_TYPE_BITMASK_LTE
+ * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead.
*/
+ @Deprecated
public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1));
/**
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index f0401534fac4..e8642fef04f5 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -204,6 +204,7 @@ public class ImsService extends Service {
private IImsServiceControllerListener mListener;
private final Object mListenerLock = new Object();
+ private final Object mExecutorLock = new Object();
private Executor mExecutor;
/**
@@ -214,10 +215,6 @@ public class ImsService extends Service {
* vendor use Runnable::run.
*/
public ImsService() {
- mExecutor = ImsService.this.getExecutor();
- if (mExecutor == null) {
- mExecutor = Runnable::run;
- }
}
/**
@@ -356,7 +353,7 @@ public class ImsService extends Service {
ImsConfigImplBase c =
ImsService.this.getConfigForSubscription(slotId, subId);
if (c != null) {
- c.setDefaultExecutor(mExecutor);
+ c.setDefaultExecutor(getCachedExecutor());
return c.getIImsConfig();
} else {
return null;
@@ -370,7 +367,7 @@ public class ImsService extends Service {
ImsRegistrationImplBase r =
ImsService.this.getRegistrationForSubscription(slotId, subId);
if (r != null) {
- r.setDefaultExecutor(mExecutor);
+ r.setDefaultExecutor(getCachedExecutor());
return r.getBinder();
} else {
return null;
@@ -383,7 +380,7 @@ public class ImsService extends Service {
return executeMethodAsyncForResult(() -> {
SipTransportImplBase s = ImsService.this.getSipTransport(slotId);
if (s != null) {
- s.setDefaultExecutor(mExecutor);
+ s.setDefaultExecutor(getCachedExecutor());
return s.getBinder();
} else {
return null;
@@ -427,11 +424,21 @@ public class ImsService extends Service {
return null;
}
+ private Executor getCachedExecutor() {
+ synchronized (mExecutorLock) {
+ if (mExecutor == null) {
+ Executor e = ImsService.this.getExecutor();
+ mExecutor = (e != null) ? e : Runnable::run;
+ }
+ return mExecutor;
+ }
+ }
+
private IImsMmTelFeature createMmTelFeatureInternal(int slotId, int subscriptionId) {
MmTelFeature f = createMmTelFeatureForSubscription(slotId, subscriptionId);
if (f != null) {
setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
- f.setDefaultExecutor(mExecutor);
+ f.setDefaultExecutor(getCachedExecutor());
return f.getBinder();
} else {
Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned.");
@@ -443,7 +450,7 @@ public class ImsService extends Service {
MmTelFeature f = createEmergencyOnlyMmTelFeature(slotId);
if (f != null) {
setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
- f.setDefaultExecutor(mExecutor);
+ f.setDefaultExecutor(getCachedExecutor());
return f.getBinder();
} else {
Log.e(LOG_TAG, "createEmergencyOnlyMmTelFeatureInternal: null feature returned.");
@@ -454,7 +461,7 @@ public class ImsService extends Service {
private IImsRcsFeature createRcsFeatureInternal(int slotId, int subI) {
RcsFeature f = createRcsFeatureForSubscription(slotId, subI);
if (f != null) {
- f.setDefaultExecutor(mExecutor);
+ f.setDefaultExecutor(getCachedExecutor());
setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
return f.getBinder();
} else {
@@ -609,7 +616,8 @@ public class ImsService extends Service {
private void executeMethodAsync(Runnable r, String errorLogName) {
try {
CompletableFuture.runAsync(
- () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r),
+ getCachedExecutor()).join();
} catch (CancellationException | CompletionException e) {
Log.w(LOG_TAG, "ImsService Binder - " + errorLogName + " exception: "
+ e.getMessage());
@@ -618,7 +626,7 @@ public class ImsService extends Service {
private <T> T executeMethodAsyncForResult(Supplier<T> r, String errorLogName) {
CompletableFuture<T> future = CompletableFuture.supplyAsync(
- () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+ () -> TelephonyUtils.runWithCleanCallingIdentity(r), getCachedExecutor());
try {
return future.get();
} catch (ExecutionException | InterruptedException e) {
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 0211a7f5c5c5..4752cca8bd6c 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -323,22 +323,20 @@ interface ISub {
*
* @param userHandle the user handle for this subscription
* @param subId the unique SubscriptionInfo index in database
- * @param callingPackage The package making the IPC.
*
* @throws SecurityException if doesn't have MANAGE_SUBSCRIPTION_USER_ASSOCIATION
* @throws IllegalArgumentException if subId is invalid.
*/
- int setUserHandle(in UserHandle userHandle, int subId, String callingPackage);
+ int setSubscriptionUserHandle(in UserHandle userHandle, int subId);
/**
* Get UserHandle for this subscription
*
* @param subId the unique SubscriptionInfo index in database
- * @param callingPackage the package making the IPC
* @return userHandle associated with this subscription.
*
- * @throws SecurityException if doesn't have SMANAGE_SUBSCRIPTION_USER_ASSOCIATION
+ * @throws SecurityException if doesn't have MANAGE_SUBSCRIPTION_USER_ASSOCIATION
* @throws IllegalArgumentException if subId is invalid.
*/
- UserHandle getUserHandle(int subId, String callingPackage);
+ UserHandle getSubscriptionUserHandle(int subId);
}
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 16753e63eb01..ca5b2afd0917 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
@@ -24,6 +24,8 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
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.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import java.util.regex.Pattern
@@ -47,9 +49,10 @@ constructor(
wmHelper: WindowManagerStateHelper,
expectedWindowName: String,
action: String?,
- stringExtras: Map<String, String>
+ stringExtras: Map<String, String>,
+ waitConditions: Array<Condition<DeviceStateDump>>
) {
- super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
+ super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras, waitConditions)
waitIMEShown(wmHelper)
}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 5cee17e5f3f9..df09e47aa946 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -30,6 +30,91 @@ using android::StringPiece;
namespace aapt {
+// This is to detect whether an <intent-filter> contains deeplink.
+// See https://developer.android.com/training/app-links/deep-linking.
+static bool HasDeepLink(xml::Element* intent_filter_el) {
+ xml::Element* action_el = intent_filter_el->FindChild({}, "action");
+ xml::Element* category_el = intent_filter_el->FindChild({}, "category");
+ xml::Element* data_el = intent_filter_el->FindChild({}, "data");
+ if (action_el == nullptr || category_el == nullptr || data_el == nullptr) {
+ return false;
+ }
+
+ // Deeplinks must specify the ACTION_VIEW intent action.
+ constexpr const char* action_view = "android.intent.action.VIEW";
+ if (intent_filter_el->FindChildWithAttribute({}, "action", xml::kSchemaAndroid, "name",
+ action_view) == nullptr) {
+ return false;
+ }
+
+ // Deeplinks must have scheme included in <data> tag.
+ xml::Attribute* data_scheme_attr = data_el->FindAttribute(xml::kSchemaAndroid, "scheme");
+ if (data_scheme_attr == nullptr || data_scheme_attr->value.empty()) {
+ return false;
+ }
+
+ // Deeplinks must include BROWSABLE category.
+ constexpr const char* category_browsable = "android.intent.category.BROWSABLE";
+ if (intent_filter_el->FindChildWithAttribute({}, "category", xml::kSchemaAndroid, "name",
+ category_browsable) == nullptr) {
+ return false;
+ }
+ return true;
+}
+
+static bool VerifyDeeplinkPathAttribute(xml::Element* data_el, android::SourcePathDiagnostics* diag,
+ const std::string& attr_name) {
+ xml::Attribute* attr = data_el->FindAttribute(xml::kSchemaAndroid, attr_name);
+ if (attr != nullptr && !attr->value.empty()) {
+ StringPiece attr_value = attr->value;
+ const char* startChar = attr_value.begin();
+ if (attr_name == "pathPattern") {
+ if (*startChar == '/' || *startChar == '.' || *startChar == '*') {
+ return true;
+ } else {
+ diag->Error(android::DiagMessage(data_el->line_number)
+ << "attribute 'android:" << attr_name << "' in <" << data_el->name
+ << "> tag has value of '" << attr_value
+ << "', it must be in a pattern start with '.' or '*', otherwise must start "
+ "with a leading slash '/'");
+ return false;
+ }
+ } else {
+ if (*startChar == '/') {
+ return true;
+ } else {
+ diag->Error(android::DiagMessage(data_el->line_number)
+ << "attribute 'android:" << attr_name << "' in <" << data_el->name
+ << "> tag has value of '" << attr_value
+ << "', it must start with a leading slash '/'");
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static bool VerifyDeepLinkIntentAction(xml::Element* intent_filter_el,
+ android::SourcePathDiagnostics* diag) {
+ if (!HasDeepLink(intent_filter_el)) {
+ return true;
+ }
+
+ xml::Element* data_el = intent_filter_el->FindChild({}, "data");
+ if (data_el != nullptr) {
+ if (!VerifyDeeplinkPathAttribute(data_el, diag, "path")) {
+ return false;
+ }
+ if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPrefix")) {
+ return false;
+ }
+ if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPattern")) {
+ return false;
+ }
+ }
+ return true;
+}
+
static bool RequiredNameIsNotEmpty(xml::Element* el, android::SourcePathDiagnostics* diag) {
xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
if (attr == nullptr) {
@@ -323,6 +408,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn
// Common <intent-filter> actions.
xml::XmlNodeAction intent_filter_action;
+ intent_filter_action.Action(VerifyDeepLinkIntentAction);
intent_filter_action["action"].Action(RequiredNameIsNotEmpty);
intent_filter_action["category"].Action(RequiredNameIsNotEmpty);
intent_filter_action["data"];
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 098d0be7f87d..cec9a1a5917e 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -1068,4 +1068,345 @@ TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) {
</manifest>)";
EXPECT_THAT(Verify(input), NotNull());
}
+
+TEST_F(ManifestFixerTest, IntentFilterActionMustHaveNonEmptyName) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
+
+TEST_F(ManifestFixerTest, IntentFilterCategoryMustHaveNonEmptyName) {
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category android:name="" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
+
+TEST_F(ManifestFixerTest, IntentFilterPathMustStartWithLeadingSlashOnDeepLinks) {
+ // No DeepLink.
+ std::string input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <data />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // No DeepLink, missing ACTION_VIEW.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink, missing DEFAULT category while DEFAULT is recommended but not required.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // No DeepLink, missing BROWSABLE category.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // No DeepLink, missing 'android:scheme' in <data> tag.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // No DeepLink, <action> is ACTION_MAIN not ACTION_VIEW.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with no leading slash in android:path.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:path="path" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // DeepLink with leading slash in android:path.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:path="/path" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with no leading slash in android:pathPrefix.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="pathPrefix" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // DeepLink with leading slash in android:pathPrefix.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPrefix="/pathPrefix" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with no leading slash in android:pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern="pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), IsNull());
+
+ // DeepLink with leading slash in android:pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern="/pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with '.' start in pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern=".*\\.pathPattern" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+
+ // DeepLink with '*' start in pathPattern.
+ input = R"(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application>
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http"
+ android:host="www.example.com"
+ android:pathPattern="*" />
+ </intent-filter>
+ </activity>
+ </application>
+ </manifest>)";
+ EXPECT_THAT(Verify(input), NotNull());
+}
} // namespace aapt