summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp76
-rw-r--r--Android.bp1
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--ProtoLibraries.bp36
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java43
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java8
-rw-r--r--api/ApiDocs.bp17
-rw-r--r--api/StubLibraries.bp4
-rw-r--r--cmds/svc/src/com/android/commands/svc/NfcCommand.java36
-rw-r--r--cmds/svc/src/com/android/commands/svc/OWNERS2
-rw-r--r--core/api/current.txt111
-rw-r--r--core/api/module-lib-current.txt22
-rw-r--r--core/api/system-current.txt17
-rw-r--r--core/api/system-lint-baseline.txt4
-rw-r--r--core/api/test-current.txt12
-rw-r--r--core/api/test-lint-baseline.txt2
-rw-r--r--core/java/Android.bp9
-rw-r--r--core/java/android/app/ContextImpl.java10
-rw-r--r--core/java/android/app/IWallpaperManager.aidl13
-rw-r--r--core/java/android/app/LocaleConfig.java40
-rw-r--r--core/java/android/app/Notification.java17
-rw-r--r--core/java/android/app/ResourcesManager.java19
-rw-r--r--core/java/android/app/WallpaperManager.java79
-rw-r--r--core/java/android/app/admin/DevicePolicyIdentifiers.java4
-rw-r--r--core/java/android/app/admin/SystemUpdatePolicy.java2
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig43
-rw-r--r--core/java/android/app/contentsuggestions/OWNERS7
-rw-r--r--core/java/android/app/servertransaction/ActivityLifecycleItem.java5
-rw-r--r--core/java/android/app/servertransaction/ClientTransaction.java103
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionItem.java7
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java169
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutorHelper.java79
-rw-r--r--core/java/android/app/usage/flags.aconfig8
-rw-r--r--core/java/android/companion/AssociationInfo.java5
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java14
-rw-r--r--core/java/android/companion/CompanionDeviceService.java8
-rw-r--r--core/java/android/companion/flags.aconfig23
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensor.java5
-rw-r--r--core/java/android/content/Intent.java21
-rw-r--r--core/java/android/content/om/FabricatedOverlay.java2
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl2
-rw-r--r--core/java/android/content/pm/LauncherApps.java24
-rw-r--r--core/java/android/content/pm/LauncherUserInfo.aidl19
-rw-r--r--core/java/android/content/pm/LauncherUserInfo.java126
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/pm/flags.aconfig4
-rw-r--r--core/java/android/content/res/ResourcesImpl.java65
-rw-r--r--core/java/android/content/res/flags.aconfig7
-rw-r--r--core/java/android/credentials/CredentialManager.java2
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java12
-rw-r--r--core/java/android/database/sqlite/SQLiteRawStatement.java2
-rw-r--r--core/java/android/database/sqlite/flags.aconfig2
-rw-r--r--core/java/android/hardware/HardwareBuffer.aidl2
-rw-r--r--core/java/android/hardware/OverlayProperties.java63
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java6
-rw-r--r--core/java/android/hardware/biometrics/CryptoObject.java44
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java19
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java18
-rw-r--r--core/java/android/hardware/flags/overlayproperties_flags.aconfig8
-rw-r--r--core/java/android/hardware/hdmi/OWNERS3
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java7
-rw-r--r--core/java/android/nfc/Constants.java29
-rw-r--r--core/java/android/nfc/NfcAdapter.java36
-rw-r--r--core/java/android/nfc/cardemulation/AidGroup.java33
-rw-r--r--core/java/android/nfc/cardemulation/ApduServiceInfo.java39
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulation.java3
-rw-r--r--core/java/android/nfc/cardemulation/NfcFServiceInfo.java58
-rw-r--r--core/java/android/nfc/flags.aconfig7
-rw-r--r--core/java/android/os/BugreportParams.java2
-rw-r--r--core/java/android/os/LocaleList.java10
-rw-r--r--core/java/android/os/OomKillRecord.java19
-rw-r--r--core/java/android/os/UserManager.java17
-rw-r--r--core/java/android/os/flags.aconfig7
-rw-r--r--core/java/android/provider/Telephony.java10
-rw-r--r--core/java/android/security/flags.aconfig1
-rw-r--r--core/java/android/service/autofill/Dataset.java3
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java8
-rw-r--r--core/java/android/service/notification/flags.aconfig9
-rw-r--r--core/java/android/service/rotationresolver/OWNERS2
-rw-r--r--core/java/android/service/voice/HotwordTrainingAudio.java12
-rw-r--r--core/java/android/service/voice/HotwordTrainingData.java14
-rw-r--r--core/java/android/text/BoringLayout.java43
-rw-r--r--core/java/android/text/ClientFlags.java15
-rw-r--r--core/java/android/text/DynamicLayout.java46
-rw-r--r--core/java/android/text/Layout.java68
-rw-r--r--core/java/android/text/StaticLayout.java89
-rw-r--r--core/java/android/text/TextFlags.java4
-rw-r--r--core/java/android/text/flags/custom_locale_fallback.aconfig9
-rw-r--r--core/java/android/text/flags/deprecate_fonts_xml.aconfig10
-rw-r--r--core/java/android/text/flags/fix_double_underline.aconfig8
-rw-r--r--core/java/android/text/flags/fix_line_height_for_locale.aconfig8
-rw-r--r--core/java/android/text/flags/flags.aconfig63
-rw-r--r--core/java/android/text/flags/no_break_no_hyphenation_span.aconfig8
-rw-r--r--core/java/android/text/flags/optimized_font_loading.aconfig11
-rw-r--r--core/java/android/text/flags/phrase_strict_fallback.aconfig8
-rw-r--r--core/java/android/text/flags/use_bounds_for_width.aconfig8
-rw-r--r--core/java/android/view/Display.java83
-rw-r--r--core/java/android/view/HapticScrollFeedbackProvider.java22
-rw-r--r--core/java/android/view/InsetsController.java6
-rw-r--r--core/java/android/view/ScrollFeedbackProvider.java29
-rw-r--r--core/java/android/view/SurfaceControl.java144
-rw-r--r--core/java/android/view/SurfaceControlRegistry.java136
-rw-r--r--core/java/android/view/View.java48
-rw-r--r--core/java/android/view/ViewConfiguration.java20
-rw-r--r--core/java/android/view/ViewRootImpl.java240
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig14
-rw-r--r--core/java/android/view/animation/AnimationUtils.java19
-rw-r--r--core/java/android/view/autofill/AutofillManager.java2
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java10
-rw-r--r--core/java/android/view/contentprotection/flags/content_protection_flags.aconfig7
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig8
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java4
-rw-r--r--core/java/android/view/inputmethod/TextAppearanceInfo.java2
-rw-r--r--core/java/android/widget/RemoteViews.java53
-rw-r--r--core/java/android/widget/TextView.java91
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig12
-rw-r--r--core/java/com/android/internal/app/PlatLogoActivity.java4
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java5
-rw-r--r--core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl1
-rw-r--r--core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java16
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java31
-rw-r--r--core/java/com/android/internal/widget/ILockSettings.aidl1
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java17
-rw-r--r--core/java/com/android/internal/widget/LockSettingsInternal.java11
-rw-r--r--core/jni/android_hardware_OverlayProperties.cpp13
-rw-r--r--core/jni/android_view_InputDevice.cpp14
-rw-r--r--core/jni/android_view_KeyCharacterMap.cpp20
-rw-r--r--core/jni/android_view_KeyCharacterMap.h2
-rw-r--r--core/jni/android_view_SurfaceControl.cpp6
-rw-r--r--core/proto/android/nfc/Android.bp43
-rw-r--r--core/res/AndroidManifest.xml3
-rw-r--r--core/res/res/values-af/strings.xml5
-rw-r--r--core/res/res/values-am/strings.xml5
-rw-r--r--core/res/res/values-ar/strings.xml5
-rw-r--r--core/res/res/values-as/strings.xml5
-rw-r--r--core/res/res/values-az/strings.xml5
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml7
-rw-r--r--core/res/res/values-be/strings.xml5
-rw-r--r--core/res/res/values-bg/strings.xml5
-rw-r--r--core/res/res/values-bn/strings.xml5
-rw-r--r--core/res/res/values-bs/strings.xml5
-rw-r--r--core/res/res/values-ca/strings.xml5
-rw-r--r--core/res/res/values-cs/strings.xml5
-rw-r--r--core/res/res/values-da/strings.xml5
-rw-r--r--core/res/res/values-de/strings.xml5
-rw-r--r--core/res/res/values-el/strings.xml7
-rw-r--r--core/res/res/values-en-rAU/strings.xml5
-rw-r--r--core/res/res/values-en-rCA/strings.xml5
-rw-r--r--core/res/res/values-en-rGB/strings.xml5
-rw-r--r--core/res/res/values-en-rIN/strings.xml5
-rw-r--r--core/res/res/values-en-rXC/strings.xml5
-rw-r--r--core/res/res/values-es-rUS/strings.xml5
-rw-r--r--core/res/res/values-es/strings.xml5
-rw-r--r--core/res/res/values-et/strings.xml5
-rw-r--r--core/res/res/values-eu/strings.xml5
-rw-r--r--core/res/res/values-fa/strings.xml5
-rw-r--r--core/res/res/values-fi/strings.xml5
-rw-r--r--core/res/res/values-fr-rCA/strings.xml5
-rw-r--r--core/res/res/values-fr/strings.xml5
-rw-r--r--core/res/res/values-gl/strings.xml5
-rw-r--r--core/res/res/values-gu/strings.xml5
-rw-r--r--core/res/res/values-hi/strings.xml5
-rw-r--r--core/res/res/values-hr/strings.xml5
-rw-r--r--core/res/res/values-hu/strings.xml5
-rw-r--r--core/res/res/values-hy/strings.xml5
-rw-r--r--core/res/res/values-in/strings.xml5
-rw-r--r--core/res/res/values-is/strings.xml5
-rw-r--r--core/res/res/values-it/strings.xml5
-rw-r--r--core/res/res/values-iw/strings.xml5
-rw-r--r--core/res/res/values-ja/strings.xml5
-rw-r--r--core/res/res/values-ka/strings.xml5
-rw-r--r--core/res/res/values-kk/strings.xml5
-rw-r--r--core/res/res/values-km/strings.xml5
-rw-r--r--core/res/res/values-kn/strings.xml5
-rw-r--r--core/res/res/values-ko/strings.xml5
-rw-r--r--core/res/res/values-ky/strings.xml7
-rw-r--r--core/res/res/values-lo/strings.xml5
-rw-r--r--core/res/res/values-lt/strings.xml5
-rw-r--r--core/res/res/values-lv/strings.xml5
-rw-r--r--core/res/res/values-mk/strings.xml5
-rw-r--r--core/res/res/values-ml/strings.xml5
-rw-r--r--core/res/res/values-mn/strings.xml5
-rw-r--r--core/res/res/values-mr/strings.xml5
-rw-r--r--core/res/res/values-ms/strings.xml5
-rw-r--r--core/res/res/values-my/strings.xml5
-rw-r--r--core/res/res/values-nb/strings.xml5
-rw-r--r--core/res/res/values-ne/strings.xml5
-rw-r--r--core/res/res/values-nl/strings.xml5
-rw-r--r--core/res/res/values-or/strings.xml5
-rw-r--r--core/res/res/values-pa/strings.xml5
-rw-r--r--core/res/res/values-pl/strings.xml5
-rw-r--r--core/res/res/values-pt-rBR/strings.xml5
-rw-r--r--core/res/res/values-pt-rPT/strings.xml5
-rw-r--r--core/res/res/values-pt/strings.xml5
-rw-r--r--core/res/res/values-ro/strings.xml5
-rw-r--r--core/res/res/values-ru/strings.xml5
-rw-r--r--core/res/res/values-si/strings.xml5
-rw-r--r--core/res/res/values-sk/strings.xml5
-rw-r--r--core/res/res/values-sl/strings.xml5
-rw-r--r--core/res/res/values-sq/strings.xml5
-rw-r--r--core/res/res/values-sr/strings.xml7
-rw-r--r--core/res/res/values-sv/strings.xml5
-rw-r--r--core/res/res/values-sw/strings.xml5
-rw-r--r--core/res/res/values-ta/strings.xml5
-rw-r--r--core/res/res/values-te/strings.xml5
-rw-r--r--core/res/res/values-th/strings.xml5
-rw-r--r--core/res/res/values-tl/strings.xml5
-rw-r--r--core/res/res/values-tr/strings.xml5
-rw-r--r--core/res/res/values-uk/strings.xml5
-rw-r--r--core/res/res/values-ur/strings.xml5
-rw-r--r--core/res/res/values-uz/strings.xml5
-rw-r--r--core/res/res/values-vi/strings.xml5
-rw-r--r--core/res/res/values-zh-rCN/strings.xml5
-rw-r--r--core/res/res/values-zh-rHK/strings.xml5
-rw-r--r--core/res/res/values-zh-rTW/strings.xml5
-rw-r--r--core/res/res/values-zu/strings.xml5
-rw-r--r--core/res/res/values/attrs.xml9
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/tests/coretests/Android.bp2
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java7
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java20
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java110
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java4
-rw-r--r--core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java12
-rw-r--r--core/tests/coretests/src/android/os/LocaleListTest.java45
-rw-r--r--core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java49
-rw-r--r--core/tests/coretests/src/android/view/OWNERS5
-rw-r--r--core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java58
-rw-r--r--core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java23
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java136
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--graphics/java/android/framework_graphics.aconfig2
-rw-r--r--graphics/java/android/graphics/Paint.java52
-rw-r--r--graphics/java/android/graphics/fonts/FontCustomizationParser.java2
-rw-r--r--graphics/java/android/graphics/fonts/FontFamily.java4
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java4
-rw-r--r--graphics/java/android/graphics/text/PositionedGlyphs.java12
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java119
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java253
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java41
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java74
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java49
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/Android.bp1
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java451
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java78
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java119
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl (renamed from packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt)22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java17
-rw-r--r--libs/WindowManager/Shell/tests/flicker/Android.bp58
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt99
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt96
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java67
-rw-r--r--libs/androidfw/AssetManager2.cpp9
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig7
-rw-r--r--libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp41
-rw-r--r--libs/hwui/pipeline/skia/SkiaMemoryTracer.h15
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp7
-rw-r--r--libs/hwui/renderthread/HintSessionWrapper.cpp1
-rw-r--r--libs/hwui/tests/unit/HintSessionWrapperTests.cpp59
-rw-r--r--location/api/current.txt4
-rw-r--r--location/java/android/location/GnssNavigationMessage.java41
-rw-r--r--location/java/android/location/flags/gnss.aconfig8
-rw-r--r--media/Android.bp6
-rw-r--r--media/java/android/media/MediaRoute2Info.java25
-rw-r--r--media/java/android/media/MediaRouter2.java70
-rw-r--r--media/java/android/media/projection/IMediaProjectionManager.aidl43
-rw-r--r--nfc-extras/OWNERS2
-rw-r--r--nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java49
-rw-r--r--packages/CredentialManager/res/values-tr/strings.xml10
-rw-r--r--packages/CredentialManager/res/values-zh-rCN/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt55
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt11
-rw-r--r--packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml6
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml7
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml1
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/Android.bp1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt17
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt42
-rw-r--r--packages/SettingsLib/res/values-af/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-am/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ar/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-as/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-az/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-b+sr+Latn/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-be/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-bg/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-cs/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-da/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-de/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-en-rAU/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-en-rCA/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-en-rGB/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-en-rIN/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-en-rXC/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-es/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-et/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-eu/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-fi/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-gl/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-gu/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-hr/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-is/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-it/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-iw/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ja/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ka/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-kk/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-km/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-kn/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ko/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ky/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-lo/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-lv/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-mk/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ml/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-mn/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ms/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-nl/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-pa/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-pl/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-pt-rBR/strings.xml3
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-pt/strings.xml3
-rw-r--r--packages/SettingsLib/res/values-ro/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ru/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-si/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-sl/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-sq/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-sr/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-sv/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-te/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-th/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-tl/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-tr/strings.xml3
-rw-r--r--packages/SettingsLib/res/values-uk/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-ur/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-zh-rHK/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-zh-rTW/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-zu/strings.xml1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java98
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java77
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java22
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java245
-rw-r--r--packages/SettingsProvider/Android.bp39
-rw-r--r--packages/SettingsProvider/AndroidManifestLib.xml2
-rw-r--r--packages/SoundPicker/res/values-af/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-am/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ar/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-as/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-az/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-b+sr+Latn/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-be/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-bg/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-bn/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-bs/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ca/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-cs/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-da/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-de/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-el/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-en-rAU/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-en-rCA/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-en-rGB/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-en-rIN/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-en-rXC/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-es-rUS/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-es/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-et/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-eu/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-fa/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-fi/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-fr-rCA/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-fr/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-gl/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-gu/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-hi/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-hr/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-hu/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-hy/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-in/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-is/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-it/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-iw/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ja/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ka/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-kk/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-km/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-kn/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ko/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ky/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-lo/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-lt/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-lv/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-mk/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ml/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-mn/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-mr/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ms/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-my/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-nb/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ne/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-nl/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-or/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-pa/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-pl/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-pt-rBR/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-pt-rPT/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-pt/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ro/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ru/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-si/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-sk/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-sl/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-sq/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-sr/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-sv/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-sw/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ta/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-te/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-th/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-tl/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-tr/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-uk/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-ur/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-uz/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-vi/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-zh-rCN/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-zh-rHK/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-zh-rTW/strings.xml3
-rw-r--r--packages/SoundPicker/res/values-zu/strings.xml3
-rw-r--r--packages/SystemUI/Android.bp13
-rw-r--r--packages/SystemUI/TEST_MAPPING16
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig16
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml2
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt17
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt126
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt1
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt92
-rw-r--r--packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt10
-rw-r--r--packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt6
-rw-r--r--packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt3
-rw-r--r--packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt4
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt123
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt40
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt20
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt17
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt1028
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt (renamed from packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt)59
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt363
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt (renamed from packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt)84
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt27
-rwxr-xr-xpackages/SystemUI/flag_check.py134
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt)0
l---------packages/SystemUI/multivalentTestsForDevice1
l---------packages/SystemUI/multivalentTestsForDeviceless1
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java10
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java48
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rAU/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rGB/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rIN/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-gl/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-iw/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-ko/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-my/strings.xml6
-rw-r--r--packages/SystemUI/res-keyguard/values-nl/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-ro/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-ru/strings.xml6
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml4
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml27
-rw-r--r--packages/SystemUI/res/values-af/strings.xml6
-rw-r--r--packages/SystemUI/res/values-am/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml8
-rw-r--r--packages/SystemUI/res/values-as/strings.xml6
-rw-r--r--packages/SystemUI/res/values-az/strings.xml6
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml22
-rw-r--r--packages/SystemUI/res/values-be/strings.xml6
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml6
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml6
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml6
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml6
-rw-r--r--packages/SystemUI/res/values-da/strings.xml6
-rw-r--r--packages/SystemUI/res/values-de/strings.xml7
-rw-r--r--packages/SystemUI/res/values-el/strings.xml6
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml6
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml4
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml6
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml6
-rw-r--r--packages/SystemUI/res/values-en-rXC/strings.xml4
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml6
-rw-r--r--packages/SystemUI/res/values-es/strings.xml6
-rw-r--r--packages/SystemUI/res/values-et/strings.xml6
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml6
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml6
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml6
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml6
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml6
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml6
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml6
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml6
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml6
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml6
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml6
-rw-r--r--packages/SystemUI/res/values-in/strings.xml6
-rw-r--r--packages/SystemUI/res/values-is/strings.xml6
-rw-r--r--packages/SystemUI/res/values-it/strings.xml6
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml6
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml6
-rw-r--r--packages/SystemUI/res/values-km/strings.xml6
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml6
-rw-r--r--packages/SystemUI/res/values-land/config.xml3
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml6
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml6
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml6
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml6
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml6
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml6
-rw-r--r--packages/SystemUI/res/values-my/strings.xml6
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml6
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml6
-rw-r--r--packages/SystemUI/res/values-or/strings.xml8
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml6
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml6
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml6
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml6
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml6
-rw-r--r--packages/SystemUI/res/values-si/strings.xml6
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml6
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml6
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml6
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml22
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml6
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml6
-rw-r--r--packages/SystemUI/res/values-te/strings.xml6
-rw-r--r--packages/SystemUI/res/values-th/strings.xml6
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml6
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml6
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml6
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml6
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml10
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml6
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml6
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml6
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml6
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml6
-rw-r--r--packages/SystemUI/res/values/config.xml16
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt)8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java19
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java3
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt50
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt3
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt54
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt335
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt69
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt122
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt35
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt6
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java202
-rw-r--r--packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt118
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt342
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt134
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt200
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt95
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt721
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt128
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt (renamed from packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt)18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt)13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt183
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java195
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt)9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt)29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt142
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt)15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt127
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt500
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt344
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt166
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt304
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java434
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java155
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt (renamed from packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt)32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt151
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt139
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/SysUITestModule.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/TestMocksModule.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt162
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java495
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt563
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt143
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt177
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt)6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt)26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt418
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt390
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt80
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt137
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt55
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt)13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt33
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt8
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt93
-rw-r--r--packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java81
-rw-r--r--packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java74
-rw-r--r--services/accessibility/accessibility.aconfig50
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java8
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java42
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java38
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java111
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java82
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java70
-rw-r--r--services/core/java/com/android/server/Watchdog.java59
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java44
-rw-r--r--services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java3
-rw-r--r--services/core/java/com/android/server/am/LowMemDetector.java17
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java13
-rw-r--r--services/core/java/com/android/server/am/ProcessProfileRecord.java24
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java11
-rw-r--r--services/core/java/com/android/server/am/ProcessServiceRecord.java6
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java2
-rw-r--r--services/core/java/com/android/server/am/UserController.java2
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/audio/AdiDeviceState.java2
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java88
-rw-r--r--services/core/java/com/android/server/camera/CameraServiceProxy.java35
-rw-r--r--services/core/java/com/android/server/display/DisplayAdapter.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayBrightnessState.java36
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java51
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerShellCommand.java100
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java39
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java10
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java6
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java10
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java12
-rw-r--r--services/core/java/com/android/server/display/config/HdrBrightnessData.java24
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java85
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java5
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java40
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java2
-rw-r--r--services/core/java/com/android/server/media/AudioAttributesUtils.java11
-rw-r--r--services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java7
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java9
-rw-r--r--services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java44
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java75
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java178
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java5
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java5
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java47
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java12
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java11
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java34
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java67
-rw-r--r--services/core/java/com/android/server/pm/ScanPackageUtils.java6
-rw-r--r--services/core/java/com/android/server/pm/Settings.java143
-rw-r--r--services/core/java/com/android/server/pm/SharedLibrariesImpl.java7
-rw-r--r--services/core/java/com/android/server/pm/SuspendPackageHelper.java8
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java19
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java14
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java6
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java11
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java24
-rw-r--r--services/core/java/com/android/server/policy/SingleKeyGestureDetector.java33
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java14
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDataParser.java51
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java779
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java2
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java15
-rw-r--r--services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java2
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java79
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java1
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java31
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java12
-rw-r--r--services/core/java/com/android/server/wm/SnapshotController.java15
-rw-r--r--services/core/java/com/android/server/wm/Task.java28
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java45
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java5
-rw-r--r--services/core/java/com/android/server/wm/UnknownAppVisibilityController.java20
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java6
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java22
-rw-r--r--services/core/java/com/android/server/wm/WindowFrames.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java10
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd8
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java12
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java10
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp16
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java38
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig14
-rw-r--r--services/java/com/android/server/SystemServer.java17
-rw-r--r--services/midi/Android.bp4
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java23
-rw-r--r--services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt14
-rw-r--r--services/tests/PackageManagerServiceTests/host/Android.bp1
-rw-r--r--services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt33
-rw-r--r--services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp8
-rw-r--r--services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml33
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java91
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java4
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java8
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java77
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java81
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java12
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java25
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java29
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java72
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java58
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java236
-rw-r--r--services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java207
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java175
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java558
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java5
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java38
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java1
-rw-r--r--services/tests/wmtests/Android.bp1
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java69
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java92
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java217
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DimmerTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java87
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java12
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java64
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java27
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java30
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java2
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java143
-rw-r--r--telephony/java/android/telephony/data/DataProfile.java2
-rw-r--r--tests/Codegen/src/com/android/codegentest/SampleDataClass.java151
-rw-r--r--tests/FlickerTests/Android.bp51
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt27
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt87
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt47
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt)4
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt)4
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt)2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt214
-rw-r--r--tests/FsVerityTest/AndroidTest.xml2
-rw-r--r--tools/codegen/src/com/android/codegen/Generators.kt12
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java3
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java2
1079 files changed, 22646 insertions, 10580 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 6d74a840525b..db515379c8b9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -17,11 +17,14 @@ aconfig_srcjars = [
":android.companion.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
+ ":android.hardware.flags-aconfig-java{.generated_srcjars}",
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+ ":android.location.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
":android.view.flags-aconfig-java{.generated_srcjars}",
":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
@@ -51,6 +54,7 @@ aconfig_srcjars = [
":aconfig_midi_flags_java_lib{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":device_policy_aconfig_flags_lib{.generated_srcjars}",
]
filegroup {
@@ -140,6 +144,21 @@ cc_aconfig_library {
aconfig_declarations: "com.android.text.flags-aconfig",
}
+// Location
+aconfig_declarations {
+ name: "android.location.flags-aconfig",
+ package: "android.location.flags",
+ srcs: [
+ "location/java/android/location/flags/*.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "android.location.flags-aconfig-java",
+ aconfig_declarations: "android.location.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// NFC
aconfig_declarations {
name: "android.nfc.flags-aconfig",
@@ -150,6 +169,11 @@ aconfig_declarations {
java_aconfig_library {
name: "android.nfc.flags-aconfig-java",
aconfig_declarations: "android.nfc.flags-aconfig",
+ min_sdk_version: "VanillaIceCream",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
+ ],
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -274,6 +298,19 @@ cc_aconfig_library {
aconfig_declarations: "android.view.accessibility.flags-aconfig",
}
+// Hardware
+aconfig_declarations {
+ name: "android.hardware.flags-aconfig",
+ package: "android.hardware.flags",
+ srcs: ["core/java/android/hardware/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.hardware.flags-aconfig-java",
+ aconfig_declarations: "android.hardware.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Widget
aconfig_declarations {
name: "android.widget.flags-aconfig",
@@ -287,6 +324,12 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+rust_aconfig_library {
+ name: "libandroid_security_flags_rust",
+ crate_name: "android_security_flags",
+ aconfig_declarations: "android.security.flags-aconfig",
+}
+
// Package Manager
aconfig_declarations {
name: "android.content.pm.flags-aconfig",
@@ -523,3 +566,36 @@ java_aconfig_library {
aconfig_declarations: "com.android.net.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// DevicePolicy
+aconfig_declarations {
+ name: "device_policy_aconfig_flags",
+ package: "android.app.admin.flags",
+ srcs: [
+ "core/java/android/app/admin/flags/flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "device_policy_aconfig_flags_lib",
+ aconfig_declarations: "device_policy_aconfig_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+cc_aconfig_library {
+ name: "device_policy_aconfig_flags_c_lib",
+ aconfig_declarations: "device_policy_aconfig_flags",
+}
+
+// Notifications
+aconfig_declarations {
+ name: "android.service.notification.flags-aconfig",
+ package: "android.service.notification",
+ srcs: ["core/java/android/service/notification/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.service.notification.flags-aconfig-java",
+ aconfig_declarations: "android.service.notification.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 0c199a634725..7d5e788c5a37 100644
--- a/Android.bp
+++ b/Android.bp
@@ -323,7 +323,6 @@ java_defaults {
":installd_aidl",
":libaudioclient_aidl",
":libbinder_aidl",
- ":libbluetooth-binder-aidl",
":libcamera_client_aidl",
":libcamera_client_framework_aidl",
":libupdate_engine_aidl",
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 015487d20f8d..b42f7bc0ca94 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -28,3 +28,6 @@ hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclu
ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES}
ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
+
+# This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py.
+flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index 45bb16184069..e7adf203334e 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -77,6 +77,42 @@ gensrcs {
output_extension: "proto.h",
}
+// ==== nfc framework java library ==============================
+gensrcs {
+ name: "framework-nfc-javastream-protos",
+
+ tools: [
+ "aprotoc",
+ "protoc-gen-javastream",
+ "soong_zip",
+ ],
+
+ cmd: "mkdir -p $(genDir)/$(in) " +
+ "&& $(location aprotoc) " +
+ " --plugin=$(location protoc-gen-javastream) " +
+ " --javastream_out=$(genDir)/$(in) " +
+ " -Iexternal/protobuf/src " +
+ " -I . " +
+ " $(in) " +
+ "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
+
+ srcs: [
+ "core/proto/android/app/pendingintent.proto",
+ "core/proto/android/content/component_name.proto",
+ "core/proto/android/content/intent.proto",
+ "core/proto/android/nfc/*.proto",
+ "core/proto/android/os/patternmatcher.proto",
+ "core/proto/android/os/persistablebundle.proto",
+ "core/proto/android/privacy.proto",
+ ],
+
+ data: [
+ ":libprotobuf-internal-protos",
+ ],
+
+ output_extension: "srcjar",
+}
+
// ==== java proto host library ==============================
java_library_host {
name: "platformprotos",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index a143d6fd4250..bbe1485ddcd4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1555,7 +1555,7 @@ public class JobSchedulerService extends com.android.server.SystemService
private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
- public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
+ public int scheduleAsPackage(JobInfo job, JobWorkItem work, int callingUid, String packageName,
int userId, @Nullable String namespace, String tag) {
// Rate limit excessive schedule() calls.
final String servicePkg = job.getService().getPackageName();
@@ -1608,12 +1608,12 @@ public class JobSchedulerService extends com.android.server.SystemService
mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
}
- if (mActivityManagerInternal.isAppStartModeDisabled(uId, servicePkg)) {
- Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
+ if (mActivityManagerInternal.isAppStartModeDisabled(callingUid, servicePkg)) {
+ Slog.w(TAG, "Not scheduling job for " + callingUid + ":" + job.toString()
+ " -- package not allowed to start");
Counter.logIncrementWithUid(
"job_scheduler.value_cntr_w_uid_schedule_failure_app_start_mode_disabled",
- uId);
+ callingUid);
return JobScheduler.RESULT_FAILURE;
}
@@ -1623,7 +1623,7 @@ public class JobSchedulerService extends com.android.server.SystemService
job.getEstimatedNetworkDownloadBytes()));
sInitialJobEstimatedNetworkUploadKBLogger.logSample(
safelyScaleBytesToKBForHistogram(job.getEstimatedNetworkUploadBytes()));
- sJobMinimumChunkKBLogger.logSampleWithUid(uId,
+ sJobMinimumChunkKBLogger.logSampleWithUid(callingUid,
safelyScaleBytesToKBForHistogram(job.getMinimumNetworkChunkBytes()));
if (work != null) {
sInitialJwiEstimatedNetworkDownloadKBLogger.logSample(
@@ -1632,7 +1632,7 @@ public class JobSchedulerService extends com.android.server.SystemService
sInitialJwiEstimatedNetworkUploadKBLogger.logSample(
safelyScaleBytesToKBForHistogram(
work.getEstimatedNetworkUploadBytes()));
- sJwiMinimumChunkKBLogger.logSampleWithUid(uId,
+ sJwiMinimumChunkKBLogger.logSampleWithUid(callingUid,
safelyScaleBytesToKBForHistogram(
work.getMinimumNetworkChunkBytes()));
}
@@ -1640,11 +1640,12 @@ public class JobSchedulerService extends com.android.server.SystemService
if (work != null) {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", uId);
+ "job_scheduler.value_cntr_w_uid_job_work_items_enqueued", callingUid);
}
synchronized (mLock) {
- final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId());
+ final JobStatus toCancel =
+ mJobs.getJobByUidAndJobId(callingUid, namespace, job.getId());
if (work != null && toCancel != null) {
// Fast path: we are adding work to an existing job, and the JobInfo is not
@@ -1664,7 +1665,7 @@ public class JobSchedulerService extends com.android.server.SystemService
// TODO(273758274): improve JobScheduler's resilience and memory management
if (toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
&& toCancel.isPersisted()) {
- Slog.w(TAG, "Too many JWIs for uid " + uId);
+ Slog.w(TAG, "Too many JWIs for uid " + callingUid);
throw new IllegalStateException("Apps may not persist more than "
+ mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+ " JobWorkItems per job");
@@ -1682,7 +1683,8 @@ public class JobSchedulerService extends com.android.server.SystemService
| JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
}
mJobs.touchJob(toCancel);
- sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, toCancel.getWorkCount());
+ sEnqueuedJwiHighWaterMarkLogger
+ .logSampleWithUid(callingUid, toCancel.getWorkCount());
// If any of work item is enqueued when the source is in the foreground,
// exempt the entire job.
@@ -1692,8 +1694,8 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- JobStatus jobStatus =
- JobStatus.createFromJobInfo(job, uId, packageName, userId, namespace, tag);
+ JobStatus jobStatus = JobStatus.createFromJobInfo(
+ job, callingUid, packageName, userId, namespace, tag);
// Return failure early if expedited job quota used up.
if (jobStatus.isRequestedExpeditedJob()) {
@@ -1702,7 +1704,7 @@ public class JobSchedulerService extends com.android.server.SystemService
&& !mQuotaController.isWithinEJQuotaLocked(jobStatus))) {
Counter.logIncrementWithUid(
"job_scheduler.value_cntr_w_uid_schedule_failure_ej_out_of_quota",
- uId);
+ callingUid);
return JobScheduler.RESULT_FAILURE;
}
}
@@ -1716,10 +1718,10 @@ public class JobSchedulerService extends com.android.server.SystemService
if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
// Jobs on behalf of others don't apply to the per-app job cap
if (packageName == null) {
- if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
- Slog.w(TAG, "Too many jobs for uid " + uId);
+ if (mJobs.countJobsForUid(callingUid) > MAX_JOBS_PER_APP) {
+ Slog.w(TAG, "Too many jobs for uid " + callingUid);
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", uId);
+ "job_scheduler.value_cntr_w_uid_max_scheduling_limit_hit", callingUid);
throw new IllegalStateException("Apps may not schedule more than "
+ MAX_JOBS_PER_APP + " distinct jobs");
}
@@ -1743,7 +1745,7 @@ public class JobSchedulerService extends com.android.server.SystemService
// TODO(273758274): improve JobScheduler's resilience and memory management
if (work != null && toCancel.isPersisted()
&& toCancel.getWorkCount() >= mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS) {
- Slog.w(TAG, "Too many JWIs for uid " + uId);
+ Slog.w(TAG, "Too many JWIs for uid " + callingUid);
throw new IllegalStateException("Apps may not persist more than "
+ mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS
+ " JobWorkItems per job");
@@ -1759,13 +1761,14 @@ public class JobSchedulerService extends com.android.server.SystemService
if (work != null) {
// If work has been supplied, enqueue it into the new job.
jobStatus.enqueueWorkLocked(work);
- sEnqueuedJwiHighWaterMarkLogger.logSampleWithUid(uId, jobStatus.getWorkCount());
+ sEnqueuedJwiHighWaterMarkLogger
+ .logSampleWithUid(callingUid, jobStatus.getWorkCount());
}
- final int sourceUid = uId;
+ final int sourceUid = jobStatus.getSourceUid();
FrameworkStatsLog.write(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
jobStatus.isProxyJob()
- ? new int[]{sourceUid, jobStatus.getUid()} : new int[]{sourceUid},
+ ? new int[]{sourceUid, callingUid} : new int[]{sourceUid},
// Given that the source tag is set by the calling app, it should be connected
// to the calling app in the attribution for a proxied job.
jobStatus.isProxyJob()
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d48d84ba6980..1fdf906fd4ea 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -75,6 +75,7 @@ import java.util.StringJoiner;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.regex.Pattern;
/**
* Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
@@ -99,6 +100,8 @@ public final class JobStore {
private static final long SCHEDULED_JOB_HIGH_WATER_MARK_PERIOD_MS = 30 * 60_000L;
@VisibleForTesting
static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+ private static final Pattern SPLIT_FILE_PATTERN =
+ Pattern.compile("^" + JOB_FILE_SPLIT_PREFIX + "\\d+.xml$");
private static final int ALL_UIDS = -1;
@VisibleForTesting
static final int INVALID_UID = -2;
@@ -1121,6 +1124,11 @@ public final class JobStore {
int numDuplicates = 0;
synchronized (mLock) {
for (File file : files) {
+ if (!file.getName().equals("jobs.xml")
+ && !SPLIT_FILE_PATTERN.matcher(file.getName()).matches()) {
+ // Skip temporary or other files.
+ continue;
+ }
final AtomicFile aFile = createJobFile(file);
try (FileInputStream fis = aFile.openRead()) {
jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index e1621008cc33..e086bfe5cbb2 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -139,9 +139,22 @@ droidstubs {
// using droiddoc
/////////////////////////////////////////////////////////////////////
-framework_docs_only_args = " -android -manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
+// doclava contains checks for a few issues that are have been migrated to metalava.
+// disable them in doclava, to avoid mistriggering or double triggering.
+ignore_doclava_errors_checked_by_metalava = "" +
+ "-hide 111 " + // HIDDEN_SUPERCLASS
+ "-hide 113 " + // DEPRECATION_MISMATCH
+ "-hide 125 " + // REQUIRES_PERMISSION
+ "-hide 126 " + // BROADCAST_BEHAVIOR
+ "-hide 127 " + // SDK_CONSTANT
+ "-hide 128 " // TODO
+
+framework_docs_only_args = "-android " +
+ "-manifest $(location :frameworks-base-core-AndroidManifest.xml) " +
"-metalavaApiSince " +
- "-werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 " +
+ "-werror " +
+ "-lerror " +
+ ignore_doclava_errors_checked_by_metalava +
"-overview $(location :frameworks-base-java-overview) " +
// Federate Support Library references against local API file.
"-federate SupportLib https://developer.android.com " +
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 7e41660cf1a2..7e78185b2659 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -739,7 +739,9 @@ java_api_library {
"android_stubs_current_contributions",
"android_system_stubs_current_contributions",
"android_test_frameworks_core_stubs_current_contributions",
- "stub-annotation-defaults",
+ ],
+ libs: [
+ "stub-annotations",
],
api_contributions: [
"api-stubs-docs-non-updatable.api.contribution",
diff --git a/cmds/svc/src/com/android/commands/svc/NfcCommand.java b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
index 020ca3387555..ee2af129d400 100644
--- a/cmds/svc/src/com/android/commands/svc/NfcCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/NfcCommand.java
@@ -16,10 +16,11 @@
package com.android.commands.svc;
+import android.app.ActivityThread;
import android.content.Context;
-import android.nfc.INfcAdapter;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
+import android.os.Looper;
public class NfcCommand extends Svc.Command {
@@ -42,27 +43,26 @@ public class NfcCommand extends Svc.Command {
@Override
public void run(String[] args) {
- INfcAdapter adapter = INfcAdapter.Stub.asInterface(
- ServiceManager.getService(Context.NFC_SERVICE));
-
+ Looper.prepareMainLooper();
+ ActivityThread.initializeMainlineModules();
+ Context context = ActivityThread.systemMain().getSystemContext();
+ NfcManager nfcManager = context.getSystemService(NfcManager.class);
+ if (nfcManager == null) {
+ System.err.println("Got a null NfcManager, is the system running?");
+ return;
+ }
+ NfcAdapter adapter = nfcManager.getDefaultAdapter();
if (adapter == null) {
System.err.println("Got a null NfcAdapter, is the system running?");
return;
}
-
- try {
- if (args.length == 2 && "enable".equals(args[1])) {
- adapter.enable();
- return;
- } else if (args.length == 2 && "disable".equals(args[1])) {
- adapter.disable(true);
- return;
- }
- } catch (RemoteException e) {
- System.err.println("NFC operation failed: " + e);
+ if (args.length == 2 && "enable".equals(args[1])) {
+ adapter.enable();
+ return;
+ } else if (args.length == 2 && "disable".equals(args[1])) {
+ adapter.disable(true);
return;
}
-
System.err.println(longHelp());
}
diff --git a/cmds/svc/src/com/android/commands/svc/OWNERS b/cmds/svc/src/com/android/commands/svc/OWNERS
new file mode 100644
index 000000000000..d5a5d7b3b858
--- /dev/null
+++ b/cmds/svc/src/com/android/commands/svc/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+per-file NfcCommand.java = file:platform/packages/apps/Nfc:/OWNERS
diff --git a/core/api/current.txt b/core/api/current.txt
index 66aeb0f7cbaf..4bcb7d77a5bb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -668,6 +668,7 @@ package android {
field public static final int debuggable = 16842767; // 0x101000f
field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562
field public static final int defaultHeight = 16844021; // 0x10104f5
+ field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
field public static final int defaultValue = 16843245; // 0x10101ed
field public static final int defaultWidth = 16844020; // 0x10104f4
@@ -6188,6 +6189,7 @@ package android.app {
ctor public LocaleConfig(@NonNull android.os.LocaleList);
method public int describeContents();
method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context);
+ method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale();
method public int getStatus();
method @Nullable public android.os.LocaleList getSupportedLocales();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -9530,7 +9532,8 @@ package android.companion {
method @Nullable public CharSequence getDisplayName();
method public int getId();
method public int getSystemDataSyncFlags();
- method @Nullable public String getTag();
+ method @FlaggedApi("android.companion.association_tag") @Nullable public String getTag();
+ method public boolean isSelfManaged();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
}
@@ -9600,7 +9603,7 @@ package android.companion {
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
- method public void clearAssociationTag(int);
+ method @FlaggedApi("android.companion.association_tag") public void clearAssociationTag(int);
method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
method public void disableSystemDataSyncForTypes(int, int);
method @Deprecated public void disassociate(@NonNull String);
@@ -9610,7 +9613,7 @@ package android.companion {
method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
- method public void setAssociationTag(int, @NonNull String);
+ method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -9641,13 +9644,13 @@ package android.companion {
method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
- method @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
- field public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
- field public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
- field public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
- field public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
- field public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
- field public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+ method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
+ field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -11719,7 +11722,7 @@ package android.content.om {
method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
- method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
+ method @FlaggedApi("android.content.res.asset_file_descriptor_frro") @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
method public void setTargetOverlayable(@Nullable String);
}
@@ -12675,6 +12678,7 @@ package android.content.pm {
method public boolean isDeviceUpgrading();
method public abstract boolean isInstantApp();
method public abstract boolean isInstantApp(@NonNull String);
+ method @FlaggedApi("android.content.pm.quarantined_enabled") public boolean isPackageQuarantined(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.stay_stopped") public boolean isPackageStopped(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isPackageSuspended(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isPackageSuspended();
@@ -12913,6 +12917,7 @@ package android.content.pm {
field public static final int MATCH_DIRECT_BOOT_UNAWARE = 262144; // 0x40000
field public static final int MATCH_DISABLED_COMPONENTS = 512; // 0x200
field public static final int MATCH_DISABLED_UNTIL_USED_COMPONENTS = 32768; // 0x8000
+ field @FlaggedApi("android.content.pm.quarantined_enabled") public static final long MATCH_QUARANTINED_COMPONENTS = 4294967296L; // 0x100000000L
field public static final int MATCH_SYSTEM_ONLY = 1048576; // 0x100000
field public static final int MATCH_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final long MAXIMUM_VERIFICATION_TIMEOUT = 3600000L; // 0x36ee80L
@@ -14352,14 +14357,14 @@ package android.database.sqlite {
public final class SQLiteDatabase extends android.database.sqlite.SQLiteClosable {
method public void beginTransaction();
method public void beginTransactionNonExclusive();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionReadOnly();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionReadOnly();
method public void beginTransactionWithListener(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener);
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener);
method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException;
method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams);
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") @NonNull public android.database.sqlite.SQLiteRawStatement createRawStatement(@NonNull String);
method public int delete(@NonNull String, @Nullable String, @Nullable String[]);
method public static boolean deleteDatabase(@NonNull java.io.File);
method public void disableWriteAheadLogging();
@@ -14370,13 +14375,13 @@ package android.database.sqlite {
method public void execSQL(@NonNull String, @NonNull Object[]) throws android.database.SQLException;
method public static String findEditTable(String);
method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastChangedRowCount();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getLastInsertRowId();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getLastInsertRowId();
method public long getMaximumSize();
method public long getPageSize();
method public String getPath();
method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
- method @FlaggedApi("android.database.sqlite.sqlite_apis_15") public long getTotalChangedRowCount();
+ method @FlaggedApi("android.database.sqlite.sqlite_apis_35") public long getTotalChangedRowCount();
method public int getVersion();
method public boolean inTransaction();
method public long insert(@NonNull String, @Nullable String, @Nullable android.content.ContentValues);
@@ -14598,7 +14603,7 @@ package android.database.sqlite {
method public int update(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues, @Nullable String, @Nullable String[]);
}
- @FlaggedApi("android.database.sqlite.sqlite_apis_15") public final class SQLiteRawStatement implements java.io.Closeable {
+ @FlaggedApi("android.database.sqlite.sqlite_apis_35") public final class SQLiteRawStatement implements java.io.Closeable {
method public void bindBlob(int, @NonNull byte[]) throws android.database.sqlite.SQLiteException;
method public void bindBlob(int, @NonNull byte[], int, int) throws android.database.sqlite.SQLiteException;
method public void bindDouble(int, double) throws android.database.sqlite.SQLiteException;
@@ -16254,6 +16259,8 @@ package android.graphics {
public static class Paint.FontMetricsInt {
ctor public Paint.FontMetricsInt();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetricsInt);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void set(@NonNull android.graphics.Paint.FontMetrics);
field public int ascent;
field public int bottom;
field public int descent;
@@ -17572,7 +17579,7 @@ package android.graphics.fonts {
ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font);
method @NonNull public android.graphics.fonts.FontFamily build();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") @Nullable public android.graphics.fonts.FontFamily buildVariableFamily();
}
public final class FontStyle {
@@ -17760,18 +17767,18 @@ package android.graphics.text {
method public float getAdvance();
method public float getAscent();
method public float getDescent();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeBold(@IntRange(from=0) int);
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public boolean getFakeItalic(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeBold(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public boolean getFakeItalic(@IntRange(from=0) int);
method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int);
method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int);
method public float getGlyphX(@IntRange(from=0) int);
method public float getGlyphY(@IntRange(from=0) int);
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getItalicOverride(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getItalicOverride(@IntRange(from=0) int);
method public float getOffsetX();
method public float getOffsetY();
- method @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public float getWeightOverride(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public float getWeightOverride(@IntRange(from=0) int);
method @IntRange(from=0) public int glyphCount();
- field @FlaggedApi("com.android.text.flags.deprecate_fonts_xml") public static final float NO_OVERRIDE = 1.4E-45f;
+ field @FlaggedApi("com.android.text.flags.new_fonts_fallback_xml") public static final float NO_OVERRIDE = 1.4E-45f;
}
public class TextRunShaper {
@@ -18168,6 +18175,13 @@ package android.hardware {
field public static final int YCBCR_P010 = 54; // 0x36
}
+ @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public final class OverlayProperties implements android.os.Parcelable {
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public int describeContents();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public boolean supportMixedColorSpaces();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public static final android.os.Parcelable.Creator<android.hardware.OverlayProperties> CREATOR;
+ }
+
public final class Sensor {
method public int getFifoMaxEventCount();
method public int getFifoReservedEventCount();
@@ -18547,12 +18561,12 @@ package android.hardware.biometrics {
ctor @Deprecated public BiometricPrompt.CryptoObject(@NonNull android.security.identity.IdentityCredential);
ctor public BiometricPrompt.CryptoObject(@NonNull android.security.identity.PresentationSession);
ctor @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") public BiometricPrompt.CryptoObject(@NonNull javax.crypto.KeyAgreement);
- method public javax.crypto.Cipher getCipher();
+ method @Nullable public javax.crypto.Cipher getCipher();
method @Deprecated @Nullable public android.security.identity.IdentityCredential getIdentityCredential();
method @FlaggedApi("android.hardware.biometrics.add_key_agreement_crypto_object") @Nullable public javax.crypto.KeyAgreement getKeyAgreement();
- method public javax.crypto.Mac getMac();
+ method @Nullable public javax.crypto.Mac getMac();
method @Nullable public android.security.identity.PresentationSession getPresentationSession();
- method public java.security.Signature getSignature();
+ method @Nullable public java.security.Signature getSignature();
}
}
@@ -23775,6 +23789,8 @@ package android.media {
field public static final int TYPE_DOCK = 13; // 0xd
field public static final int TYPE_GROUP = 2000; // 0x7d0
field public static final int TYPE_HDMI = 9; // 0x9
+ field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_ARC = 10; // 0xa
+ field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_EARC = 29; // 0x1d
field public static final int TYPE_HEARING_AID = 23; // 0x17
field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb
field public static final int TYPE_REMOTE_CAR = 1008; // 0x3f0
@@ -23974,7 +23990,7 @@ package android.media {
method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
method public void registerControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.ControllerCallback);
method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+ method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void registerRouteListingPreferenceUpdatedCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
@@ -23983,7 +23999,7 @@ package android.media {
method public void transferTo(@NonNull android.media.MediaRoute2Info);
method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceCallback(@NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
+ method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void unregisterRouteListingPreferenceUpdatedCallback(@NonNull java.util.function.Consumer<android.media.RouteListingPreference>);
method public void unregisterTransferCallback(@NonNull android.media.MediaRouter2.TransferCallback);
}
@@ -24004,11 +24020,6 @@ package android.media {
method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
}
- @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public abstract static class MediaRouter2.RouteListingPreferenceCallback {
- ctor @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public MediaRouter2.RouteListingPreferenceCallback();
- method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") public void onRouteListingPreferenceChanged(@Nullable android.media.RouteListingPreference);
- }
-
public class MediaRouter2.RoutingController {
method public void deselectRoute(@NonNull android.media.MediaRoute2Info);
method @Nullable public android.os.Bundle getControlHints();
@@ -33464,7 +33475,7 @@ package android.os {
field public static final String DISALLOW_MICROPHONE_TOGGLE = "disallow_microphone_toggle";
field public static final String DISALLOW_MODIFY_ACCOUNTS = "no_modify_accounts";
field public static final String DISALLOW_MOUNT_PHYSICAL_MEDIA = "no_physical_media";
- field public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
+ field @FlaggedApi("android.nfc.enable_nfc_user_restriction") public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO = "no_near_field_communication_radio";
field public static final String DISALLOW_NETWORK_RESET = "no_network_reset";
field public static final String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
field public static final String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
@@ -43116,6 +43127,7 @@ package android.telephony {
field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool";
field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
+ field @FlaggedApi("com.android.internal.telephony.flags.hide_roaming_icon") public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool";
field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool";
field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool";
field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
@@ -45472,7 +45484,6 @@ package android.telephony {
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT = 9; // 0x9
field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED = 6; // 0x6
- field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16; // 0x10
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
field public static final int SET_OPPORTUNISTIC_SUB_NO_OPPORTUNISTIC_SUB_AVAILABLE = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION = 4; // 0x4
@@ -45702,6 +45713,7 @@ package android.telephony.data {
field public static final int TYPE_IMS = 64; // 0x40
field public static final int TYPE_MCX = 1024; // 0x400
field public static final int TYPE_MMS = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000
field public static final int TYPE_SUPL = 4; // 0x4
field public static final int TYPE_VSIM = 4096; // 0x1000
field public static final int TYPE_XCAP = 2048; // 0x800
@@ -46710,6 +46722,7 @@ package android.text {
method @NonNull public android.text.DynamicLayout.Builder setJustificationMode(int);
method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.text.DynamicLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.DynamicLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.DynamicLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.DynamicLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.DynamicLayout.Builder setUseBoundsForWidth(boolean);
method @NonNull public android.text.DynamicLayout.Builder setUseLineSpacingFromFallbacks(boolean);
@@ -46898,6 +46911,7 @@ package android.text {
method public int getLineVisibleEnd(int);
method public float getLineWidth(int);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @IntRange(from=1) public final int getMaxLines();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public int getOffsetForHorizontal(int, float);
method public int getOffsetToLeftOf(int);
method public int getOffsetToRightOf(int);
@@ -46964,6 +46978,7 @@ package android.text {
method @NonNull public android.text.Layout.Builder setLineSpacingAmount(float);
method @NonNull public android.text.Layout.Builder setLineSpacingMultiplier(@FloatRange(from=0) float);
method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.Layout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
@@ -47235,6 +47250,7 @@ package android.text {
method @NonNull public android.text.StaticLayout.Builder setLineBreakConfig(@NonNull android.graphics.text.LineBreakConfig);
method @NonNull public android.text.StaticLayout.Builder setLineSpacing(float, @FloatRange(from=0.0) float);
method @NonNull public android.text.StaticLayout.Builder setMaxLines(@IntRange(from=0) int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @NonNull public android.text.StaticLayout.Builder setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public android.text.StaticLayout.Builder setText(CharSequence);
method @NonNull public android.text.StaticLayout.Builder setTextDirection(@NonNull android.text.TextDirectionHeuristic);
method @FlaggedApi("com.android.text.flags.use_bounds_for_width") @NonNull public android.text.StaticLayout.Builder setUseBoundsForWidth(boolean);
@@ -49844,6 +49860,7 @@ package android.view {
method public android.view.Display.Mode getMode();
method public String getName();
method @Deprecated public int getOrientation();
+ method @FlaggedApi("android.hardware.flags.overlayproperties_class_api") @NonNull public android.hardware.OverlayProperties getOverlaySupport();
method @Deprecated public int getPixelFormat();
method @Nullable public android.graphics.ColorSpace getPreferredWideGamutColorSpace();
method public long getPresentationDeadlineNanos();
@@ -50122,13 +50139,6 @@ package android.view {
field public static final int VIRTUAL_KEY_RELEASE = 8; // 0x8
}
- @FlaggedApi("android.view.flags.scroll_feedback_api") public class HapticScrollFeedbackProvider implements android.view.ScrollFeedbackProvider {
- ctor public HapticScrollFeedbackProvider(@NonNull android.view.View);
- method public void onScrollLimit(int, int, int, boolean);
- method public void onScrollProgress(int, int, int, int);
- method public void onSnapToItem(int, int, int);
- }
-
public class InflateException extends java.lang.RuntimeException {
ctor public InflateException();
ctor public InflateException(String, Throwable);
@@ -51295,9 +51305,10 @@ package android.view {
}
@FlaggedApi("android.view.flags.scroll_feedback_api") public interface ScrollFeedbackProvider {
- method public void onScrollLimit(int, int, int, boolean);
- method public void onScrollProgress(int, int, int, int);
- method public void onSnapToItem(int, int, int);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") @NonNull public static android.view.ScrollFeedbackProvider createProvider(@NonNull android.view.View);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollLimit(int, int, int, boolean);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onScrollProgress(int, int, int, int);
+ method @FlaggedApi("android.view.flags.scroll_feedback_api") public void onSnapToItem(int, int, int);
}
public class SearchEvent {
@@ -52579,7 +52590,6 @@ package android.view {
method @Deprecated public static int getEdgeSlop();
method @Deprecated public static int getFadingEdgeLength();
method @Deprecated public static long getGlobalActionKeyTimeout();
- method @FlaggedApi("android.view.flags.scroll_feedback_api") public int getHapticScrollFeedbackTickInterval(int, int, int);
method public static int getJumpTapTimeout();
method public static int getKeyRepeatDelay();
method public static int getKeyRepeatTimeout();
@@ -52619,7 +52629,6 @@ package android.view {
method @Deprecated public static int getWindowTouchSlop();
method public static long getZoomControlsTimeout();
method public boolean hasPermanentMenuKey();
- method @FlaggedApi("android.view.flags.scroll_feedback_api") public boolean isHapticScrollFeedbackEnabled(int, int, int);
method public boolean shouldShowMenuShortcutsWhenKeyboardPresent();
}
@@ -54512,8 +54521,8 @@ package android.view.animation {
public class AnimationUtils {
ctor public AnimationUtils();
method public static long currentAnimationTimeMillis();
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeMillis();
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static long getExpectedPresentationTimeNanos();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeMillis();
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static long getExpectedPresentationTimeNanos();
method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException;
method public static android.view.animation.LayoutAnimationController loadLayoutAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -59920,6 +59929,7 @@ package android.widget {
method public int getMinHeight();
method public int getMinLines();
method public int getMinWidth();
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") @Nullable public android.graphics.Paint.FontMetrics getMinimumFontMetrics();
method public final android.text.method.MovementMethod getMovementMethod();
method public int getOffsetForPosition(float, float);
method public android.text.TextPaint getPaint();
@@ -60056,6 +60066,7 @@ package android.widget {
method public void setMinHeight(int);
method public void setMinLines(int);
method public void setMinWidth(int);
+ method @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") public void setMinimumFontMetrics(@Nullable android.graphics.Paint.FontMetrics);
method public final void setMovementMethod(android.text.method.MovementMethod);
method public void setOnEditorActionListener(android.widget.TextView.OnEditorActionListener);
method public void setPaintFlags(int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b5d3ed7c8a7e..2af3c34bf2c4 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -6,7 +6,7 @@ package android {
field public static final String CONTROL_AUTOMOTIVE_GNSS = "android.permission.CONTROL_AUTOMOTIVE_GNSS";
field public static final String GET_INTENT_SENDER_INTENT = "android.permission.GET_INTENT_SENDER_INTENT";
field public static final String MAKE_UID_VISIBLE = "android.permission.MAKE_UID_VISIBLE";
- field public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
+ field @FlaggedApi("android.companion.flags.companion_transport_apis") public static final String USE_COMPANION_TRANSPORTS = "android.permission.USE_COMPANION_TRANSPORTS";
}
}
@@ -85,21 +85,21 @@ package android.app.admin {
package android.companion {
public final class CompanionDeviceManager {
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
- method @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
- field public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
- field public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
- field public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnMessageReceivedListener(@NonNull java.util.concurrent.Executor, int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void addOnTransportsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnMessageReceivedListener(int, @NonNull android.companion.CompanionDeviceManager.OnMessageReceivedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void removeOnTransportsChangedListener(@NonNull android.companion.CompanionDeviceManager.OnTransportsChangedListener);
+ method @FlaggedApi("android.companion.companion_transport_apis") @RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS) public void sendMessage(int, @NonNull byte[], @NonNull int[]);
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 1667729539; // 0x63678883
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 1669491075; // 0x63826983
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 1669494629; // 0x63827765
}
- public static interface CompanionDeviceManager.OnMessageReceivedListener {
+ @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnMessageReceivedListener {
method public void onMessageReceived(int, @NonNull byte[]);
}
- public static interface CompanionDeviceManager.OnTransportsChangedListener {
+ @FlaggedApi("android.companion.companion_transport_apis") public static interface CompanionDeviceManager.OnTransportsChangedListener {
method public void onTransportsChanged(@NonNull java.util.List<android.companion.AssociationInfo>);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5d1f6dc3e675..1a7810adc5b4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3149,7 +3149,6 @@ package android.companion {
public final class AssociationInfo implements android.os.Parcelable {
method @NonNull public String getPackageName();
- method public boolean isSelfManaged();
}
public final class CompanionDeviceManager {
@@ -9538,7 +9537,7 @@ package android.net.wifi.sharedconnectivity.app {
method public int getDeviceType();
method @NonNull public android.os.Bundle getExtras();
method @NonNull public String getModelName();
- method public boolean isBatteryCharging();
+ method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR;
field public static final int DEVICE_TYPE_AUTO = 5; // 0x5
@@ -9552,7 +9551,7 @@ package android.net.wifi.sharedconnectivity.app {
public static final class NetworkProviderInfo.Builder {
ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build();
- method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
+ method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String);
@@ -9641,6 +9640,7 @@ package android.nfc {
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
+ method @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setReaderMode(boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int setTagIntentAppPreferenceForUser(int, @NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
field public static final int TAG_INTENT_APP_PREF_RESULT_PACKAGE_NOT_FOUND = -1; // 0xffffffff
@@ -10482,7 +10482,7 @@ package android.os {
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
- method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
+ method public boolean isUserVisible();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public void setBootUser(@NonNull android.os.UserHandle);
@@ -12744,7 +12744,7 @@ package android.service.voice {
method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int);
}
- public final class HotwordTrainingAudio implements android.os.Parcelable {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingAudio implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.media.AudioFormat getAudioFormat();
method @NonNull public int getAudioType();
@@ -12755,7 +12755,7 @@ package android.service.voice {
field public static final int HOTWORD_OFFSET_UNSET = -1; // 0xffffffff
}
- public static final class HotwordTrainingAudio.Builder {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingAudio.Builder {
ctor public HotwordTrainingAudio.Builder(@NonNull byte[], @NonNull android.media.AudioFormat);
method @NonNull public android.service.voice.HotwordTrainingAudio build();
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setAudioFormat(@NonNull android.media.AudioFormat);
@@ -12764,7 +12764,7 @@ package android.service.voice {
method @NonNull public android.service.voice.HotwordTrainingAudio.Builder setHotwordOffsetMillis(int);
}
- public final class HotwordTrainingData implements android.os.Parcelable {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public final class HotwordTrainingData implements android.os.Parcelable {
method public int describeContents();
method public static int getMaxTrainingDataBytes();
method public int getTimeoutStage();
@@ -12773,7 +12773,7 @@ package android.service.voice {
field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordTrainingData> CREATOR;
}
- public static final class HotwordTrainingData.Builder {
+ @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") public static final class HotwordTrainingData.Builder {
ctor public HotwordTrainingData.Builder();
method @NonNull public android.service.voice.HotwordTrainingData.Builder addTrainingAudio(@NonNull android.service.voice.HotwordTrainingAudio);
method @NonNull public android.service.voice.HotwordTrainingData build();
@@ -14717,6 +14717,7 @@ package android.telephony.data {
field public static final String TYPE_IMS_STRING = "ims";
field public static final String TYPE_MCX_STRING = "mcx";
field public static final String TYPE_MMS_STRING = "mms";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
field public static final String TYPE_SUPL_STRING = "supl";
field public static final String TYPE_VSIM_STRING = "vsim";
field public static final String TYPE_XCAP_STRING = "xcap";
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 4fb7b6bddaac..a5010312e764 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -251,10 +251,6 @@ UnflaggedApi: android.media.audiopolicy.AudioMixingRule#writeToParcel(android.os
New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioMixingRule.writeToParcel(android.os.Parcel,int)
UnflaggedApi: android.media.audiopolicy.AudioPolicy#updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>):
New API must be flagged with @FlaggedApi: method android.media.audiopolicy.AudioPolicy.updateMixingRules(java.util.List<android.util.Pair<android.media.audiopolicy.AudioMix,android.media.audiopolicy.AudioMixingRule>>)
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo#isBatteryCharging():
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.isBatteryCharging()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder#setBatteryCharging(boolean):
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder.setBatteryCharging(boolean)
UnflaggedApi: android.nfc.cardemulation.AidGroup#CONTENTS_FILE_DESCRIPTOR:
New API must be flagged with @FlaggedApi: field android.nfc.cardemulation.AidGroup.CONTENTS_FILE_DESCRIPTOR
UnflaggedApi: android.nfc.cardemulation.AidGroup#PARCELABLE_WRITE_RETURN_VALUE:
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 384b9573528e..779777c7658a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -541,7 +541,7 @@ package android.app.admin {
field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods";
field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended";
field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled";
- field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
+ field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
}
public class DevicePolicyManager {
@@ -851,13 +851,13 @@ package android.companion {
method @NonNull public android.companion.AssociationInfo.Builder setRevoked(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSelfManaged(boolean);
method @NonNull public android.companion.AssociationInfo.Builder setSystemDataSyncFlags(int);
- method @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
+ method @FlaggedApi("android.companion.association_tag") @NonNull public android.companion.AssociationInfo.Builder setTag(@Nullable String);
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
public final class CompanionDeviceManager {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
- field public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
+ field @FlaggedApi("android.companion.companion_transport_apis") public static final int MESSAGE_REQUEST_PING = 1669362552; // 0x63807378
}
public abstract class CompanionDeviceService extends android.app.Service {
@@ -2117,7 +2117,7 @@ package android.net.wifi.sharedconnectivity.app {
public class SharedConnectivityManager {
method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String);
- method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
+ method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver();
method @Nullable public android.content.ServiceConnection getServiceConnection();
method public void setService(@Nullable android.os.IInterface);
}
@@ -2148,7 +2148,7 @@ package android.os {
}
public final class BugreportParams {
- field public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
+ field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
}
public class Build {
@@ -3622,7 +3622,7 @@ package android.view.accessibility {
package android.view.animation {
public class AnimationUtils {
- method @FlaggedApi("android.view.flags.expected_presentation_time_api") public static void lockAnimationClock(long, long);
+ method @FlaggedApi("android.view.flags.expected_presentation_time_read_only") public static void lockAnimationClock(long, long);
method public static void unlockAnimationClock();
}
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 93e39d5f41f7..105e7645ac4b 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -257,8 +257,6 @@ UnflaggedApi: android.media.soundtrigger.SoundTriggerManager#loadSoundModel(andr
New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.loadSoundModel(android.hardware.soundtrigger.SoundTrigger.SoundModel)
UnflaggedApi: android.media.soundtrigger.SoundTriggerManager.Model#getSoundModel():
New API must be flagged with @FlaggedApi: method android.media.soundtrigger.SoundTriggerManager.Model.getSoundModel()
-UnflaggedApi: android.net.wifi.sharedconnectivity.app.SharedConnectivityManager#getBroadcastReceiver():
- New API must be flagged with @FlaggedApi: method android.net.wifi.sharedconnectivity.app.SharedConnectivityManager.getBroadcastReceiver()
UnflaggedApi: android.os.BatteryManager#BATTERY_PLUGGED_ANY:
New API must be flagged with @FlaggedApi: field android.os.BatteryManager.BATTERY_PLUGGED_ANY
UnflaggedApi: android.os.BugreportParams#BUGREPORT_MODE_MAX_VALUE:
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 0293f66061c1..ddb221f422d9 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -14,6 +14,15 @@ aidl_library {
hdrs: ["android/hardware/HardwareBuffer.aidl"],
}
+// TODO (b/303286040): Remove this once |ENABLE_NFC_MAINLINE_FLAG| is rolled out
+filegroup {
+ name: "framework-core-nfc-infcadapter-sources",
+ srcs: [
+ "android/nfc/INfcAdapter.aidl",
+ ],
+ visibility: ["//frameworks/base/services/core"],
+}
+
filegroup {
name: "framework-core-sources",
srcs: [
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index a538247998e6..08c18c8b7448 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@ class ContextImpl extends Context {
// only do this if the user already has more than one preferred locale
if (r.getConfiguration().getLocales().size() > 1) {
- LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
- mResourcesManager.setLocaleList(lc != null
- && lc.getSupportedLocales() != null
- && !lc.getSupportedLocales().isEmpty()
- ? lc.getSupportedLocales()
- : null);
+ LocaleConfig lc = getUserId() < 0
+ ? LocaleConfig.fromContextIgnoringOverride(this)
+ : new LocaleConfig(this);
+ mResourcesManager.setLocaleConfig(lc);
}
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index a3b82e935673..d7d654672abc 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -161,12 +161,6 @@ interface IWallpaperManager {
*/
boolean isWallpaperBackupEligible(int which, int userId);
- /*
- * Keyguard: register a callback for being notified that lock-state relevant
- * wallpaper content has changed.
- */
- boolean setLockWallpaperCallback(IWallpaperManagerCallback cb);
-
/**
* Returns the colors used by the lock screen or system wallpaper.
*
@@ -253,13 +247,6 @@ interface IWallpaperManager {
boolean isStaticWallpaper(int which);
/**
- * Temporary method for project b/197814683.
- * Return true if the lockscreen wallpaper always uses a WallpaperService, not a static image.
- * @hide
- */
- boolean isLockscreenLiveWallpaperEnabled();
-
- /**
* Temporary method for project b/270726737.
* Return true if the wallpaper supports different crops for different display dimensions.
* @hide
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 1fdc51687433..369a78144fd3 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -16,9 +16,11 @@
package android.app;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -30,6 +32,7 @@ import android.util.AttributeSet;
import android.util.Slog;
import android.util.Xml;
+import com.android.internal.R;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -39,7 +42,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -66,6 +69,8 @@ public class LocaleConfig implements Parcelable {
public static final String TAG_LOCALE_CONFIG = "locale-config";
public static final String TAG_LOCALE = "locale";
private LocaleList mLocales;
+
+ private Locale mDefaultLocale;
private int mStatus = STATUS_NOT_SPECIFIED;
/**
@@ -193,8 +198,17 @@ public class LocaleConfig implements Parcelable {
XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
int outerDepth = parser.getDepth();
AttributeSet attrs = Xml.asAttributeSet(parser);
- // LinkedHashSet to preserve insertion order
- Set<String> localeNames = new LinkedHashSet<>();
+
+ String defaultLocale = null;
+ if (android.content.res.Flags.defaultLocale()) {
+ TypedArray att = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.LocaleConfig);
+ defaultLocale = att.getString(
+ R.styleable.LocaleConfig_defaultLocale);
+ att.recycle();
+ }
+
+ Set<String> localeNames = new HashSet<>();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_LOCALE.equals(parser.getName())) {
final TypedArray attributes = res.obtainAttributes(
@@ -209,6 +223,15 @@ public class LocaleConfig implements Parcelable {
}
mStatus = STATUS_SUCCESS;
mLocales = LocaleList.forLanguageTags(String.join(",", localeNames));
+ if (defaultLocale != null) {
+ if (localeNames.contains(defaultLocale)) {
+ mDefaultLocale = Locale.forLanguageTag(defaultLocale);
+ } else {
+ Slog.w(TAG, "Default locale specified that is not contained in the list: "
+ + defaultLocale);
+ mStatus = STATUS_PARSING_FAILED;
+ }
+ }
}
/**
@@ -224,6 +247,17 @@ public class LocaleConfig implements Parcelable {
}
/**
+ * Returns the default locale if specified, otherwise null
+ *
+ * @return The default Locale or null
+ */
+ @SuppressLint("UseIcu")
+ @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE)
+ public @Nullable Locale getDefaultLocale() {
+ return mDefaultLocale;
+ }
+
+ /**
* Get the status of reading the resource file where the LocaleConfig was stored.
*
* <p>Distinguish "the application didn't provide the resource file" from "the application
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 94e1292a7554..3bde39c03f25 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5607,7 +5607,8 @@ public class Notification implements Parcelable
// Use different highlighted colors for conversations' unread count
if (p.mHighlightExpander) {
pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
- textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
+ textColor = Colors.flattenAlpha(
+ getColors(p).getOnTertiaryAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -12833,7 +12834,7 @@ public class Notification implements Parcelable
private int mPrimaryAccentColor = COLOR_INVALID;
private int mSecondaryAccentColor = COLOR_INVALID;
private int mTertiaryAccentColor = COLOR_INVALID;
- private int mOnAccentTextColor = COLOR_INVALID;
+ private int mOnTertiaryAccentTextColor = COLOR_INVALID;
private int mErrorColor = COLOR_INVALID;
private int mContrastColor = COLOR_INVALID;
private int mRippleAlpha = 0x33;
@@ -12908,7 +12909,7 @@ public class Notification implements Parcelable
mPrimaryAccentColor = mPrimaryTextColor;
mSecondaryAccentColor = mSecondaryTextColor;
mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
- mOnAccentTextColor = mBackgroundColor;
+ mOnTertiaryAccentTextColor = mBackgroundColor;
mErrorColor = mPrimaryTextColor;
mRippleAlpha = 0x33;
} else {
@@ -12930,7 +12931,7 @@ public class Notification implements Parcelable
mPrimaryAccentColor = getColor(ta, 3, COLOR_INVALID);
mSecondaryAccentColor = getColor(ta, 4, COLOR_INVALID);
mTertiaryAccentColor = getColor(ta, 5, COLOR_INVALID);
- mOnAccentTextColor = getColor(ta, 6, COLOR_INVALID);
+ mOnTertiaryAccentTextColor = getColor(ta, 6, COLOR_INVALID);
mErrorColor = getColor(ta, 7, COLOR_INVALID);
mRippleAlpha = Color.alpha(getColor(ta, 8, 0x33ffffff));
}
@@ -12955,8 +12956,8 @@ public class Notification implements Parcelable
if (mTertiaryAccentColor == COLOR_INVALID) {
mTertiaryAccentColor = mContrastColor;
}
- if (mOnAccentTextColor == COLOR_INVALID) {
- mOnAccentTextColor = ColorUtils.setAlphaComponent(
+ if (mOnTertiaryAccentTextColor == COLOR_INVALID) {
+ mOnTertiaryAccentTextColor = ColorUtils.setAlphaComponent(
ContrastColorUtil.resolvePrimaryColor(
ctx, mTertiaryAccentColor, nightMode), 0xFF);
}
@@ -13029,8 +13030,8 @@ public class Notification implements Parcelable
}
/** @return the theme's text color to be used on the tertiary accent color. */
- public @ColorInt int getOnAccentTextColor() {
- return mOnAccentTextColor;
+ public @ColorInt int getOnTertiaryAccentTextColor() {
+ return mOnTertiaryAccentTextColor;
}
/**
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1ecb5d33fba2..6009c29ae53c 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@ public class ResourcesManager {
private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>();
/**
- * The list of locales the app declares it supports.
+ * The localeConfig of the app.
*/
- private LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+ private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
private static class ApkKey {
public final String path;
@@ -1612,18 +1612,19 @@ public class ResourcesManager {
}
/**
- * Returns the LocaleList current set
+ * Returns the LocaleConfig current set
*/
- public LocaleList getLocaleList() {
- return mLocaleList;
+ public LocaleConfig getLocaleConfig() {
+ return mLocaleConfig;
}
/**
- * Sets the LocaleList of app's supported locales
+ * Sets the LocaleConfig of the app
*/
- public void setLocaleList(LocaleList localeList) {
- if ((localeList != null) && !localeList.isEmpty()) {
- mLocaleList = localeList;
+ public void setLocaleConfig(LocaleConfig localeConfig) {
+ if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null)
+ && !localeConfig.getSupportedLocales().isEmpty()) {
+ mLocaleConfig = localeConfig;
}
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index e7a5b72eafc5..d660078a9ae7 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -313,7 +313,6 @@ public class WallpaperManager {
private final Context mContext;
private final boolean mWcgEnabled;
private final ColorManagementProxy mCmProxy;
- private static Boolean sIsLockscreenLiveWallpaperEnabled = null;
private static Boolean sIsMultiCropEnabled = null;
/**
@@ -841,29 +840,14 @@ public class WallpaperManager {
}
/**
+ * TODO (b/305908217) remove
* Temporary method for project b/197814683.
* @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
* @hide
*/
@TestApi
public boolean isLockscreenLiveWallpaperEnabled() {
- return isLockscreenLiveWallpaperEnabledHelper();
- }
-
- private static boolean isLockscreenLiveWallpaperEnabledHelper() {
- if (sGlobals == null) {
- sIsLockscreenLiveWallpaperEnabled = SystemProperties.getBoolean(
- "persist.wm.debug.lockscreen_live_wallpaper", true);
- }
- if (sIsLockscreenLiveWallpaperEnabled == null) {
- try {
- sIsLockscreenLiveWallpaperEnabled =
- sGlobals.mService.isLockscreenLiveWallpaperEnabled();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- return sIsLockscreenLiveWallpaperEnabled;
+ return true;
}
/**
@@ -2446,12 +2430,7 @@ public class WallpaperManager {
*/
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
public void clearWallpaper() {
- if (isLockscreenLiveWallpaperEnabled()) {
- clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
- return;
- }
- clearWallpaper(FLAG_LOCK, mContext.getUserId());
- clearWallpaper(FLAG_SYSTEM, mContext.getUserId());
+ clearWallpaper(FLAG_LOCK | FLAG_SYSTEM, mContext.getUserId());
}
/**
@@ -2787,11 +2766,7 @@ public class WallpaperManager {
*/
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
public void clear() throws IOException {
- if (isLockscreenLiveWallpaperEnabled()) {
- clear(FLAG_SYSTEM | FLAG_LOCK);
- return;
- }
- setStream(openDefaultWallpaper(mContext, FLAG_SYSTEM), null, false);
+ clear(FLAG_SYSTEM | FLAG_LOCK);
}
/**
@@ -2816,16 +2791,7 @@ public class WallpaperManager {
*/
@RequiresPermission(android.Manifest.permission.SET_WALLPAPER)
public void clear(@SetWallpaperFlags int which) throws IOException {
- if (isLockscreenLiveWallpaperEnabled()) {
- clearWallpaper(which, mContext.getUserId());
- return;
- }
- if ((which & FLAG_SYSTEM) != 0) {
- clear();
- }
- if ((which & FLAG_LOCK) != 0) {
- clearWallpaper(FLAG_LOCK, mContext.getUserId());
- }
+ clearWallpaper(which, mContext.getUserId());
}
/**
@@ -2840,16 +2806,12 @@ public class WallpaperManager {
public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) {
final String whichProp;
final int defaultResId;
- if (which == FLAG_LOCK && !isLockscreenLiveWallpaperEnabledHelper()) {
- /* Factory-default lock wallpapers are not yet supported
- whichProp = PROP_LOCK_WALLPAPER;
- defaultResId = com.android.internal.R.drawable.default_lock_wallpaper;
- */
- return null;
- } else {
- whichProp = PROP_WALLPAPER;
- defaultResId = com.android.internal.R.drawable.default_wallpaper;
- }
+ /* Factory-default lock wallpapers are not yet supported.
+ whichProp = which == FLAG_LOCK ? PROP_LOCK_WALLPAPER : PROP_WALLPAPER;
+ defaultResId = which == FLAG_LOCK ? R.drawable.default_lock_wallpaper : ....
+ */
+ whichProp = PROP_WALLPAPER;
+ defaultResId = R.drawable.default_wallpaper;
final String path = SystemProperties.get(whichProp);
final InputStream wallpaperInputStream = getWallpaperInputStream(path);
if (wallpaperInputStream != null) {
@@ -2988,25 +2950,6 @@ public class WallpaperManager {
}
/**
- * Register a callback for lock wallpaper observation. Only the OS may use this.
- *
- * @return true on success; false on error.
- * @hide
- */
- public boolean setLockWallpaperCallback(IWallpaperManagerCallback callback) {
- if (sGlobals.mService == null) {
- Log.w(TAG, "WallpaperService not running");
- throw new RuntimeException(new DeadSystemException());
- }
-
- try {
- return sGlobals.mService.setLockWallpaperCallback(callback);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Is the current system wallpaper eligible for backup?
*
* Only the OS itself may use this method.
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index ad0af72c72b4..84b1ca5c6a61 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -16,10 +16,13 @@
package android.app.admin;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.UserManager;
+
import java.util.Objects;
/**
@@ -164,6 +167,7 @@ public final class DevicePolicyIdentifiers {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED)
@TestApi
public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling";
diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java
index 113a6dd0ca48..7320cea16451 100644
--- a/core/java/android/app/admin/SystemUpdatePolicy.java
+++ b/core/java/android/app/admin/SystemUpdatePolicy.java
@@ -51,7 +51,7 @@ import java.util.stream.Collectors;
/**
* Determines when over-the-air system updates are installed on a device. Only a device policy
* controller (DPC) running in device owner mode or in profile owner mode for an organization-owned
- * device can set an update policy for the device—by calling the {@code DevicePolicyManager} method
+ * device can set an update policy for the device by calling the {@code DevicePolicyManager} method
* {@link DevicePolicyManager#setSystemUpdatePolicy setSystemUpdatePolicy()}. An update
* policy affects the pending system update (if there is one) and any future updates for the device.
*
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
new file mode 100644
index 000000000000..5a41c65535e6
--- /dev/null
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -0,0 +1,43 @@
+package: "android.app.admin.flags"
+
+flag {
+ name: "policy_engine_migration_v2_enabled"
+ namespace: "enterprise"
+ description: "V2 of the policy engine migrations for Android V"
+ bug: "289520697"
+}
+
+flag {
+ name: "device_policy_size_tracking_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track the total policy size and have a max threshold."
+ bug: "281543351"
+}
+
+flag {
+ name: "onboarding_bugreport_v2_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track required changes for enabled V2 of auto-capturing of onboarding bug reports."
+ bug: "302517677"
+}
+
+flag {
+ name: "cross_user_suspension_enabled"
+ namespace: "enterprise"
+ description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
+ bug: "263464464"
+}
+
+flag {
+ name: "dedicated_device_control_enabled"
+ namespace: "enterprise"
+ description: "Allow the device management role holder to control which platform features are available on dedicated devices."
+ bug: "281964214"
+}
+
+flag {
+ name: "security_log_v2_enabled"
+ namespace: "enterprise"
+ description: "Improve access to security logging in the context of Zero Trust."
+ bug: "295324350"
+}
diff --git a/core/java/android/app/contentsuggestions/OWNERS b/core/java/android/app/contentsuggestions/OWNERS
index cf54c2a6fcbc..5f8de77c9dda 100644
--- a/core/java/android/app/contentsuggestions/OWNERS
+++ b/core/java/android/app/contentsuggestions/OWNERS
@@ -1,7 +1,4 @@
# Bug component: 643919
-augale@google.com
-joannechung@google.com
-markpun@google.com
-lpeter@google.com
-tymtsai@google.com
+hackz@google.com
+volnov@google.com
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index b34f6788fb60..06bff5df490a 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -58,6 +58,11 @@ public abstract class ActivityLifecycleItem extends ActivityTransactionItem {
super(in);
}
+ @Override
+ boolean isActivityLifecycleItem() {
+ return true;
+ }
+
/** A final lifecycle state that an activity should reach. */
@LifecycleState
public abstract int getTargetState();
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 8617386516af..9c0cd39e8102 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -45,6 +45,13 @@ import java.util.Objects;
*/
public class ClientTransaction implements Parcelable, ObjectPoolItem {
+ /**
+ * List of transaction items that should be executed in order. Including both
+ * {@link ActivityLifecycleItem} and other {@link ClientTransactionItem}.
+ */
+ @Nullable
+ private List<ClientTransactionItem> mTransactionItems;
+
/** A list of individual callbacks to a client. */
@UnsupportedAppUsage
private List<ClientTransactionItem> mActivityCallbacks;
@@ -64,9 +71,32 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
}
/**
- * Add a message to the end of the sequence of callbacks.
+ * Adds a message to the end of the sequence of transaction items.
+ * @param item A single message that can contain a client activity/window request/callback.
+ * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
+ */
+ public void addTransactionItem(@NonNull ClientTransactionItem item) {
+ if (mTransactionItems == null) {
+ mTransactionItems = new ArrayList<>();
+ }
+ mTransactionItems.add(item);
+ }
+
+ /**
+ * Gets the list of client window requests/callbacks.
+ * TODO(b/260873529): must be non null after remove the deprecated methods.
+ */
+ @Nullable
+ public List<ClientTransactionItem> getTransactionItems() {
+ return mTransactionItems;
+ }
+
+ /**
+ * Adds a message to the end of the sequence of callbacks.
* @param activityCallback A single message that can contain a lifecycle request/callback.
+ * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
*/
+ @Deprecated
public void addCallback(@NonNull ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
@@ -74,25 +104,35 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
mActivityCallbacks.add(activityCallback);
}
- /** Get the list of callbacks. */
+ /**
+ * Gets the list of callbacks.
+ * @deprecated use {@link #getTransactionItems()} instead.
+ */
@Nullable
@VisibleForTesting
@UnsupportedAppUsage
+ @Deprecated
public List<ClientTransactionItem> getCallbacks() {
return mActivityCallbacks;
}
- /** Get the target state lifecycle request. */
+ /**
+ * Gets the target state lifecycle request.
+ * @deprecated use {@link #getTransactionItems()} instead.
+ */
@VisibleForTesting(visibility = PACKAGE)
@UnsupportedAppUsage
+ @Deprecated
public ActivityLifecycleItem getLifecycleStateRequest() {
return mLifecycleStateRequest;
}
/**
- * Set the lifecycle state in which the client should be after executing the transaction.
+ * Sets the lifecycle state in which the client should be after executing the transaction.
* @param stateRequest A lifecycle request initialized with right parameters.
+ * @deprecated use {@link #addTransactionItem(ClientTransactionItem)} instead.
*/
+ @Deprecated
public void setLifecycleStateRequest(@NonNull ActivityLifecycleItem stateRequest) {
mLifecycleStateRequest = stateRequest;
}
@@ -103,6 +143,14 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
* requested by transaction items.
*/
public void preExecute(@NonNull ClientTransactionHandler clientTransactionHandler) {
+ if (mTransactionItems != null) {
+ final int size = mTransactionItems.size();
+ for (int i = 0; i < size; ++i) {
+ mTransactionItems.get(i).preExecute(clientTransactionHandler);
+ }
+ return;
+ }
+
if (mActivityCallbacks != null) {
final int size = mActivityCallbacks.size();
for (int i = 0; i < size; ++i) {
@@ -147,6 +195,13 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
@Override
public void recycle() {
+ if (mTransactionItems != null) {
+ int size = mTransactionItems.size();
+ for (int i = 0; i < size; i++) {
+ mTransactionItems.get(i).recycle();
+ }
+ mTransactionItems = null;
+ }
if (mActivityCallbacks != null) {
int size = mActivityCallbacks.size();
for (int i = 0; i < size; i++) {
@@ -165,8 +220,15 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
// Parcelable implementation
/** Write to Parcel. */
+ @SuppressWarnings("AndroidFrameworkEfficientParcelable") // Item class is not final.
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ final boolean writeTransactionItems = mTransactionItems != null;
+ dest.writeBoolean(writeTransactionItems);
+ if (writeTransactionItems) {
+ dest.writeParcelableList(mTransactionItems, flags);
+ }
+
dest.writeParcelable(mLifecycleStateRequest, flags);
final boolean writeActivityCallbacks = mActivityCallbacks != null;
dest.writeBoolean(writeActivityCallbacks);
@@ -177,11 +239,20 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
/** Read from Parcel. */
private ClientTransaction(@NonNull Parcel in) {
- mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(), android.app.servertransaction.ActivityLifecycleItem.class);
+ final boolean readTransactionItems = in.readBoolean();
+ if (readTransactionItems) {
+ mTransactionItems = new ArrayList<>();
+ in.readParcelableList(mTransactionItems, getClass().getClassLoader(),
+ ClientTransactionItem.class);
+ }
+
+ mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader(),
+ ActivityLifecycleItem.class);
final boolean readActivityCallbacks = in.readBoolean();
if (readActivityCallbacks) {
mActivityCallbacks = new ArrayList<>();
- in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(), android.app.servertransaction.ClientTransactionItem.class);
+ in.readParcelableList(mActivityCallbacks, getClass().getClassLoader(),
+ ClientTransactionItem.class);
}
}
@@ -209,7 +280,8 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
return false;
}
final ClientTransaction other = (ClientTransaction) o;
- return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+ return Objects.equals(mTransactionItems, other.mTransactionItems)
+ && Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
&& Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
&& mClient == other.mClient;
}
@@ -217,6 +289,7 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
@Override
public int hashCode() {
int result = 17;
+ result = 31 * result + Objects.hashCode(mTransactionItems);
result = 31 * result + Objects.hashCode(mActivityCallbacks);
result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
result = 31 * result + Objects.hashCode(mClient);
@@ -227,6 +300,22 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
pw.append(prefix).println("ClientTransaction{");
+ if (mTransactionItems != null) {
+ pw.append(prefix).print(" transactionItems=[");
+ final String itemPrefix = prefix + " ";
+ final int size = mTransactionItems.size();
+ if (size > 0) {
+ pw.println();
+ for (int i = 0; i < size; i++) {
+ mTransactionItems.get(i).dump(itemPrefix, pw, transactionHandler);
+ }
+ pw.append(prefix).println(" ]");
+ } else {
+ pw.println("]");
+ }
+ pw.append(prefix).println("}");
+ return;
+ }
pw.append(prefix).print(" callbacks=[");
final String itemPrefix = prefix + " ";
final int size = mActivityCallbacks != null ? mActivityCallbacks.size() : 0;
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 07e5a7dc5f02..f94e22de06e5 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -72,6 +72,13 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel
return null;
}
+ /**
+ * Whether this is a {@link ActivityLifecycleItem}.
+ */
+ boolean isActivityLifecycleItem() {
+ return false;
+ }
+
/** Dumps this transaction item. */
void dump(@NonNull String prefix, @NonNull PrintWriter pw,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 066f9fe84970..9f5e0dc14cca 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -28,6 +28,7 @@ import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
import static android.app.servertransaction.TransactionExecutorHelper.getShortActivityName;
import static android.app.servertransaction.TransactionExecutorHelper.getStateName;
import static android.app.servertransaction.TransactionExecutorHelper.lastCallbackRequestingState;
+import static android.app.servertransaction.TransactionExecutorHelper.shouldExcludeLastLifecycleState;
import static android.app.servertransaction.TransactionExecutorHelper.tId;
import static android.app.servertransaction.TransactionExecutorHelper.transactionToString;
@@ -61,6 +62,9 @@ public class TransactionExecutor {
private final PendingTransactionActions mPendingActions = new PendingTransactionActions();
private final TransactionExecutorHelper mHelper = new TransactionExecutorHelper();
+ /** Keeps track of display ids whose Configuration got updated within a transaction. */
+ private final ArraySet<Integer> mConfigUpdatedDisplayIds = new ArraySet<>();
+
/** Initialize an instance with transaction handler, that will execute all requested actions. */
public TransactionExecutor(@NonNull ClientTransactionHandler clientTransactionHandler) {
mTransactionHandler = clientTransactionHandler;
@@ -79,15 +83,52 @@ public class TransactionExecutor {
Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
}
- executeCallbacks(transaction);
- executeLifecycleState(transaction);
+ if (transaction.getTransactionItems() != null) {
+ executeTransactionItems(transaction);
+ } else {
+ // TODO(b/260873529): cleanup after launch.
+ executeCallbacks(transaction);
+ executeLifecycleState(transaction);
+ }
+
+ if (!mConfigUpdatedDisplayIds.isEmpty()) {
+ // Whether this transaction should trigger DisplayListener#onDisplayChanged.
+ final ClientTransactionListenerController controller =
+ ClientTransactionListenerController.getInstance();
+ final int displayCount = mConfigUpdatedDisplayIds.size();
+ for (int i = 0; i < displayCount; i++) {
+ final int displayId = mConfigUpdatedDisplayIds.valueAt(i);
+ controller.onDisplayChanged(displayId);
+ }
+ mConfigUpdatedDisplayIds.clear();
+ }
mPendingActions.clear();
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
}
- /** Cycle through all states requested by callbacks and execute them at proper times. */
+ /** Cycles through all transaction items and execute them at proper times. */
@VisibleForTesting
+ public void executeTransactionItems(@NonNull ClientTransaction transaction) {
+ final List<ClientTransactionItem> items = transaction.getTransactionItems();
+ final int size = items.size();
+ for (int i = 0; i < size; i++) {
+ final ClientTransactionItem item = items.get(i);
+ if (item.isActivityLifecycleItem()) {
+ executeLifecycleItem(transaction, (ActivityLifecycleItem) item);
+ } else {
+ executeNonLifecycleItem(transaction, item,
+ shouldExcludeLastLifecycleState(items, i));
+ }
+ }
+ }
+
+ /**
+ * Cycle through all states requested by callbacks and execute them at proper times.
+ * @deprecated use {@link #executeTransactionItems} instead.
+ */
+ @VisibleForTesting
+ @Deprecated
public void executeCallbacks(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null || callbacks.isEmpty()) {
@@ -105,83 +146,78 @@ public class TransactionExecutor {
// Index of the last callback that requests some post-execution state.
final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
- // Keep track of display ids whose Configuration got updated with this transaction.
- ArraySet<Integer> configUpdatedDisplays = null;
-
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
- final IBinder token = item.getActivityToken();
- ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
-
- if (token != null && r == null
- && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
- // The activity has not been created but has been requested to destroy, so all
- // transactions for the token are just like being cancelled.
- Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
- continue;
- }
- if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+ // Skip the very last transition and perform it by explicit state request instead.
final int postExecutionState = item.getPostExecutionState();
+ final boolean shouldExcludeLastLifecycleState = postExecutionState != UNDEFINED
+ && i == lastCallbackRequestingState && finalState == postExecutionState;
+ executeNonLifecycleItem(transaction, item, shouldExcludeLastLifecycleState);
+ }
+ }
- if (item.shouldHaveDefinedPreExecutionState()) {
- final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
- item.getPostExecutionState());
- if (closestPreExecutionState != UNDEFINED) {
- cycleToPath(r, closestPreExecutionState, transaction);
- }
- }
+ private void executeNonLifecycleItem(@NonNull ClientTransaction transaction,
+ @NonNull ClientTransactionItem item, boolean shouldExcludeLastLifecycleState) {
+ final IBinder token = item.getActivityToken();
+ ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
- // Can't read flag from isolated process.
- final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
- && syncWindowConfigUpdateFlag();
- final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
- ? item.getContextToUpdate(mTransactionHandler)
- : null;
- final Configuration preExecutedConfig = configUpdatedContext != null
- ? new Configuration(configUpdatedContext.getResources().getConfiguration())
- : null;
-
- item.execute(mTransactionHandler, mPendingActions);
-
- if (configUpdatedContext != null) {
- final Configuration postExecutedConfig = configUpdatedContext.getResources()
- .getConfiguration();
- if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
- if (configUpdatedDisplays == null) {
- configUpdatedDisplays = new ArraySet<>();
- }
- configUpdatedDisplays.add(configUpdatedContext.getDisplayId());
- }
- }
+ if (token != null && r == null
+ && mTransactionHandler.getActivitiesToBeDestroyed().containsKey(token)) {
+ // The activity has not been created but has been requested to destroy, so all
+ // transactions for the token are just like being cancelled.
+ Slog.w(TAG, "Skip pre-destroyed transaction item:\n" + item);
+ return;
+ }
- item.postExecute(mTransactionHandler, mPendingActions);
- if (r == null) {
- // Launch activity request will create an activity record.
- r = mTransactionHandler.getActivityClient(token);
- }
+ if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
+ final int postExecutionState = item.getPostExecutionState();
- if (postExecutionState != UNDEFINED && r != null) {
- // Skip the very last transition and perform it by explicit state request instead.
- final boolean shouldExcludeLastTransition =
- i == lastCallbackRequestingState && finalState == postExecutionState;
- cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
+ if (item.shouldHaveDefinedPreExecutionState()) {
+ final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+ postExecutionState);
+ if (closestPreExecutionState != UNDEFINED) {
+ cycleToPath(r, closestPreExecutionState, transaction);
}
}
- if (configUpdatedDisplays != null) {
- final ClientTransactionListenerController controller =
- ClientTransactionListenerController.getInstance();
- final int displayCount = configUpdatedDisplays.size();
- for (int i = 0; i < displayCount; i++) {
- final int displayId = configUpdatedDisplays.valueAt(i);
- controller.onDisplayChanged(displayId);
+ // Can't read flag from isolated process.
+ final boolean isSyncWindowConfigUpdateFlagEnabled = !Process.isIsolated()
+ && syncWindowConfigUpdateFlag();
+ final Context configUpdatedContext = isSyncWindowConfigUpdateFlagEnabled
+ ? item.getContextToUpdate(mTransactionHandler)
+ : null;
+ final Configuration preExecutedConfig = configUpdatedContext != null
+ ? new Configuration(configUpdatedContext.getResources().getConfiguration())
+ : null;
+
+ item.execute(mTransactionHandler, mPendingActions);
+
+ if (configUpdatedContext != null) {
+ final Configuration postExecutedConfig = configUpdatedContext.getResources()
+ .getConfiguration();
+ if (!areConfigurationsEqualForDisplay(postExecutedConfig, preExecutedConfig)) {
+ mConfigUpdatedDisplayIds.add(configUpdatedContext.getDisplayId());
}
}
+
+ item.postExecute(mTransactionHandler, mPendingActions);
+ if (r == null) {
+ // Launch activity request will create an activity record.
+ r = mTransactionHandler.getActivityClient(token);
+ }
+
+ if (postExecutionState != UNDEFINED && r != null) {
+ cycleToPath(r, postExecutionState, shouldExcludeLastLifecycleState, transaction);
+ }
}
- /** Transition to the final state if requested by the transaction. */
+ /**
+ * Transition to the final state if requested by the transaction.
+ * @deprecated use {@link #executeTransactionItems} instead
+ */
+ @Deprecated
private void executeLifecycleState(@NonNull ClientTransaction transaction) {
final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
if (lifecycleItem == null) {
@@ -189,6 +225,11 @@ public class TransactionExecutor {
return;
}
+ executeLifecycleItem(transaction, lifecycleItem);
+ }
+
+ private void executeLifecycleItem(@NonNull ClientTransaction transaction,
+ @NonNull ActivityLifecycleItem lifecycleItem) {
final IBinder token = lifecycleItem.getActivityToken();
final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
if (DEBUG_RESOLVER) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 7e89a5b45a2d..dfbccb41d045 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -236,21 +236,39 @@ public class TransactionExecutorHelper {
* index 1 will be returned, because ActivityResult request on position 1 will be the last
* request that moves activity to the RESUMED state where it will eventually end.
*/
- static int lastCallbackRequestingState(ClientTransaction transaction) {
+ static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
- if (callbacks == null || callbacks.size() == 0) {
+ if (callbacks == null || callbacks.isEmpty()
+ || transaction.getLifecycleStateRequest() == null) {
return -1;
}
+ return lastCallbackRequestingStateIndex(callbacks, 0, callbacks.size() - 1,
+ transaction.getLifecycleStateRequest().getActivityToken());
+ }
+ /**
+ * Returns the index of the last callback between the start index and last index that requests
+ * the state for the given activity token in which that activity will be after execution.
+ * If there is a group of callbacks in the end that requests the same specific state or doesn't
+ * request any - we will find the first one from such group.
+ *
+ * E.g. ActivityResult requests RESUMED post-execution state, Configuration does not request any
+ * specific state. If there is a sequence
+ * Configuration - ActivityResult - Configuration - ActivityResult
+ * index 1 will be returned, because ActivityResult request on position 1 will be the last
+ * request that moves activity to the RESUMED state where it will eventually end.
+ */
+ private static int lastCallbackRequestingStateIndex(@NonNull List<ClientTransactionItem> items,
+ int startIndex, int lastIndex, @NonNull IBinder activityToken) {
// Go from the back of the list to front, look for the request closes to the beginning that
// requests the state in which activity will end after all callbacks are executed.
int lastRequestedState = UNDEFINED;
int lastRequestingCallback = -1;
- for (int i = callbacks.size() - 1; i >= 0; i--) {
- final ClientTransactionItem callback = callbacks.get(i);
- final int postExecutionState = callback.getPostExecutionState();
- if (postExecutionState != UNDEFINED) {
- // Found a callback that requests some post-execution state.
+ for (int i = lastIndex; i >= startIndex; i--) {
+ final ClientTransactionItem item = items.get(i);
+ final int postExecutionState = item.getPostExecutionState();
+ if (postExecutionState != UNDEFINED && activityToken.equals(item.getActivityToken())) {
+ // Found a callback that requests some post-execution state for the given activity.
if (lastRequestedState == UNDEFINED || lastRequestedState == postExecutionState) {
// It's either a first-from-end callback that requests state or it requests
// the same state as the last one. In both cases, we will use it as the new
@@ -266,6 +284,53 @@ public class TransactionExecutorHelper {
return lastRequestingCallback;
}
+ /**
+ * For the transaction item at {@code currentIndex}, if it is requesting post execution state,
+ * whether or not to exclude the last state. This only returns {@code true} when there is a
+ * following explicit {@link ActivityLifecycleItem} requesting the same state for the same
+ * activity, so that last state will be covered by the following {@link ActivityLifecycleItem}.
+ */
+ static boolean shouldExcludeLastLifecycleState(@NonNull List<ClientTransactionItem> items,
+ int currentIndex) {
+ final ClientTransactionItem item = items.get(currentIndex);
+ final IBinder activityToken = item.getActivityToken();
+ final int postExecutionState = item.getPostExecutionState();
+ if (activityToken == null || postExecutionState == UNDEFINED) {
+ // Not a transaction item requesting post execution state.
+ return false;
+ }
+ final int nextLifecycleItemIndex = findNextLifecycleItemIndex(items, currentIndex + 1,
+ activityToken);
+ if (nextLifecycleItemIndex == -1) {
+ // No following ActivityLifecycleItem for this activity token.
+ return false;
+ }
+ final ActivityLifecycleItem lifecycleItem =
+ (ActivityLifecycleItem) items.get(nextLifecycleItemIndex);
+ if (postExecutionState != lifecycleItem.getTargetState()) {
+ // The explicit ActivityLifecycleItem is not requesting the same state.
+ return false;
+ }
+ // Only exclude for the first non-lifecycle item that requests the same specific state.
+ return currentIndex == lastCallbackRequestingStateIndex(items, currentIndex,
+ nextLifecycleItemIndex - 1, activityToken);
+ }
+
+ /**
+ * Finds the index of the next {@link ActivityLifecycleItem} for the given activity token.
+ */
+ private static int findNextLifecycleItemIndex(@NonNull List<ClientTransactionItem> items,
+ int startIndex, @NonNull IBinder activityToken) {
+ final int size = items.size();
+ for (int i = startIndex; i < size; i++) {
+ final ClientTransactionItem item = items.get(i);
+ if (item.isActivityLifecycleItem() && item.getActivityToken().equals(activityToken)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
/** Dump transaction to string. */
static String transactionToString(@NonNull ClientTransaction transaction,
@NonNull ClientTransactionHandler transactionHandler) {
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index f401a7607364..4f1c65b1e395 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -13,3 +13,11 @@ flag {
description: "Feature flag for the new REPORT_USAGE_STATS permission."
bug: "296056771"
}
+
+flag {
+ name: "use_dedicated_handler_thread"
+ namespace: "backstage_power"
+ description: "Flag to use a dedicated thread for usage event process"
+ is_fixed_read_only: true
+ bug: "299336442"
+}
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 6393c456bdcd..161fa799f2f5 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -144,6 +144,7 @@ public final class AssociationInfo implements Parcelable {
* @return the tag of this association.
* @see CompanionDeviceManager#setAssociationTag(int, String)
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@Nullable
public String getTag() {
return mTag;
@@ -205,9 +206,8 @@ public final class AssociationInfo implements Parcelable {
/**
* @return whether the association is managed by the companion application it belongs to.
* @see AssociationRequest.Builder#setSelfManaged(boolean)
- * @hide
*/
- @SystemApi
+ @SuppressLint("UnflaggedApi") // promoting from @SystemApi
public boolean isSelfManaged() {
return mSelfManaged;
}
@@ -459,6 +459,7 @@ public final class AssociationInfo implements Parcelable {
}
/** @hide */
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@TestApi
@NonNull
public Builder setTag(@Nullable String tag) {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index a84845a15a79..70811bb329ec 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -216,12 +217,14 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@TestApi public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
/**
* Message header assigned to the remote authentication handshakes.
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public static final int MESSAGE_REQUEST_REMOTE_AUTHENTICATION = 0x63827765; // ?RMA
/**
@@ -229,6 +232,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
/**
@@ -236,6 +240,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
@@ -873,6 +878,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public interface OnTransportsChangedListener {
/**
@@ -892,6 +898,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnTransportsChangedListener(
@@ -913,6 +920,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnTransportsChangedListener(
@@ -934,6 +942,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void sendMessage(int messageType, @NonNull byte[] data, @NonNull int[] associationIds) {
@@ -951,6 +960,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
public interface OnMessageReceivedListener {
/**
@@ -964,6 +974,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnMessageReceivedListener(
@@ -983,6 +994,7 @@ public final class CompanionDeviceManager {
*
* @hide
*/
+ @FlaggedApi(Flags.FLAG_COMPANION_TRANSPORT_APIS)
@SystemApi(client = MODULE_LIBRARIES)
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnMessageReceivedListener(int messageType,
@@ -1423,6 +1435,7 @@ public final class CompanionDeviceManager {
* of the companion device recorded by CompanionDeviceManager
* @param tag the tag of this association
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void setAssociationTag(int associationId, @NonNull String tag) {
Objects.requireNonNull(tag, "tag cannot be null");
@@ -1447,6 +1460,7 @@ public final class CompanionDeviceManager {
* of the companion device recorded by CompanionDeviceManager
* @see CompanionDeviceManager#setAssociationTag(int, String)
*/
+ @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
@UserHandleAware
public void clearAssociationTag(int associationId) {
try {
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 570ecaa47b4e..c99a45764de7 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -17,6 +17,7 @@
package android.companion;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
@@ -140,24 +141,28 @@ public abstract class CompanionDeviceService extends Service {
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event if the device comes into BLE range.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BLE_APPEARED = 0;
/**
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event if the device is no longer in BLE range.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1;
/**
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event when the bluetooth device is connected.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BT_CONNECTED = 2;
/**
* Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
* with this event if the bluetooth device is disconnected.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
/**
@@ -165,6 +170,7 @@ public abstract class CompanionDeviceService extends Service {
* {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
* own.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
/**
@@ -172,6 +178,7 @@ public abstract class CompanionDeviceService extends Service {
* {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
* its own.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
private final Stub mRemote = new Stub();
@@ -348,6 +355,7 @@ public abstract class CompanionDeviceService extends Service {
* @param associationInfo A record for the companion device.
* @param event Associated companion device's event.
*/
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
@MainThread
public void onDeviceEvent(@NonNull AssociationInfo associationInfo,
@DeviceEvent int event) {
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index b9e5609171c3..6e33dff3a379 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -5,4 +5,25 @@ flag {
namespace: "companion"
description: "Controls if the new Builder is exposed to test apis."
bug: "296251481"
-} \ No newline at end of file
+}
+
+flag {
+ name: "companion_transport_apis"
+ namespace: "companion"
+ description: "Grants access to the companion transport apis."
+ bug: "288297505"
+}
+
+flag {
+ name: "association_tag"
+ namespace: "companion"
+ description: "Enable Association tag APIs "
+ bug: "289241123"
+}
+
+flag {
+ name: "device_presence"
+ namespace: "companion"
+ description: "Enable device presence APIs"
+ bug: "283000075"
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index eaa17925b14b..14c799763a96 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -115,6 +115,11 @@ public final class VirtualSensor implements Parcelable {
parcel.writeStrongBinder(mToken);
}
+ @Override
+ public String toString() {
+ return "VirtualSensor{ mType=" + mType + ", mName='" + mName + "' }";
+ }
+
/**
* Send a sensor event to the system.
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b765562ab587..7b6bad31539a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -64,6 +64,7 @@ import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCommand;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.provider.ContactsContract.QuickContact;
@@ -2796,6 +2797,8 @@ public class Intent implements Parcelable, Cloneable {
* started and is no longer considered stopped.
* <ul>
* <li> {@link #EXTRA_UID} containing the integer uid assigned to the package.
+ * <li> {@link #EXTRA_TIME} containing the {@link SystemClock#elapsedRealtime()
+ * elapsed realtime} of when the package was unstopped.
* </ul>
*
* <p class="note">This is a protected intent that can only be sent by the system.
@@ -2869,9 +2872,15 @@ public class Intent implements Parcelable, Cloneable {
*
* <p class="note">This is a protected intent that can only be sent
* by the system.
+ * <p>
+ * Starting in {@link Build.VERSION_CODES#VANILLA_ICE_CREAM Android V}, an extra timestamp
+ * {@link #EXTRA_TIME} is included with this broadcast to indicate the exact time the package
+ * was restarted, in {@link SystemClock#elapsedRealtime() elapsed realtime}.
+ * </p>
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
+
/**
* Broadcast Action: The user has cleared the data of a package. This should
* be preceded by {@link #ACTION_PACKAGE_RESTARTED}, after which all of
@@ -4283,6 +4292,14 @@ public class Intent implements Parcelable, Cloneable {
"com.android.intent.action.SHOW_BRIGHTNESS_DIALOG";
/**
+ * Intent Extra: holds boolean that determines whether brightness dialog is full width when
+ * in landscape mode.
+ * @hide
+ */
+ public static final String EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH =
+ "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH";
+
+ /**
* Activity Action: Shows the contrast setting dialog.
* @hide
*/
@@ -6578,8 +6595,8 @@ public class Intent implements Parcelable, Cloneable {
= "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
/**
- * Optional extra specifying a time in milliseconds since the Epoch. The value must be
- * non-negative.
+ * Optional extra specifying a time in milliseconds. The timebase depends on the Intent
+ * including this extra. The value must be non-negative.
* <p>
* Type: long
* </p>
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index c4547b8acc2b..df2d7e70880f 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,6 +16,7 @@
package android.content.om;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -546,6 +547,7 @@ public class FabricatedOverlay {
* @param configuration The string representation of the config this overlay is enabled for
*/
@NonNull
+ @FlaggedApi(android.content.res.Flags.FLAG_ASSET_FILE_DESCRIPTOR_FRRO)
public void setResourceValue(
@NonNull String resourceName,
@NonNull AssetFileDescriptor value,
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 563ed7dd6e7a..e9f419e9a8ce 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
import android.content.LocusId;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.LauncherActivityInfoInternal;
@@ -62,6 +63,7 @@ interface ILauncherApps {
in Bundle opts, in UserHandle user);
PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
in UserHandle user);
+ LauncherUserInfo getLauncherUserInfo(in UserHandle user);
void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
String callingFeatureId, in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index dbaa4c93d71c..0cd4358b2c91 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission;
import static android.Manifest.permission.READ_FRAME_BUFFER;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +56,7 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Flags;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -776,6 +778,28 @@ public class LauncherApps {
}
/**
+ * Returns information related to a user which is useful for displaying UI elements
+ * to distinguish it from other users (eg, badges). Only system launchers should
+ * call this API.
+ *
+ * @param userHandle user handle of the user for which LauncherUserInfo is requested
+ * @return the LauncherUserInfo object related to the user specified.
+ * @hide
+ */
+ @Nullable
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) {
+ if (DEBUG) {
+ Log.i(TAG, "getLauncherUserInfo " + userHandle);
+ }
+ try {
+ return mService.getLauncherUserInfo(userHandle);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
* returns null.
*
diff --git a/core/java/android/content/pm/LauncherUserInfo.aidl b/core/java/android/content/pm/LauncherUserInfo.aidl
new file mode 100644
index 000000000000..f875f1ef0c6d
--- /dev/null
+++ b/core/java/android/content/pm/LauncherUserInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.pm;
+
+parcelable LauncherUserInfo;
diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java
new file mode 100644
index 000000000000..214c3e48db71
--- /dev/null
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+/**
+ * The LauncherUserInfo object holds information about an Android user that is required to display
+ * the Launcher related UI elements specific to the user (like badges).
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+public final class LauncherUserInfo implements Parcelable {
+
+ private final String mUserType;
+
+ // Serial number for the user, should be same as in the {@link UserInfo} object.
+ private final int mUserSerialNumber;
+
+ /**
+ * Returns type of the user as defined in {@link UserManager}. e.g.,
+ * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE}
+ * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug
+ * is resolved.
+ *
+ * @return the userType for the user whose LauncherUserInfo this is
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ @NonNull
+ public String getUserType() {
+ return mUserType;
+ }
+
+ /**
+ * Returns serial number of user as returned by
+ * {@link UserManager#getSerialNumberForUser(UserHandle)}
+ *
+ * @return the serial number associated with the user
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public int getUserSerialNumber() {
+ return mUserSerialNumber;
+ }
+
+ private LauncherUserInfo(@NonNull Parcel in) {
+ mUserType = in.readString16NoHelper();
+ mUserSerialNumber = in.readInt();
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString16NoHelper(mUserType);
+ dest.writeInt(mUserSerialNumber);
+ }
+
+ @Override
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public int describeContents() {
+ return 0;
+ }
+
+ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+ public static final @android.annotation.NonNull Creator<LauncherUserInfo> CREATOR =
+ new Creator<LauncherUserInfo>() {
+ @Override
+ public LauncherUserInfo createFromParcel(Parcel in) {
+ return new LauncherUserInfo(in);
+ }
+
+ @Override
+ public LauncherUserInfo[] newArray(int size) {
+ return new LauncherUserInfo[size];
+ }
+ };
+
+ /**
+ * @hide
+ */
+ public static final class Builder {
+ private final String mUserType;
+
+ private final int mUserSerialNumber;
+
+ public Builder(@NonNull String userType, int userSerialNumber) {
+ this.mUserType = userType;
+ this.mUserSerialNumber = userSerialNumber;
+ }
+
+ /**
+ * Builds the LauncherUserInfo object
+ */
+ @NonNull public LauncherUserInfo build() {
+ return new LauncherUserInfo(this.mUserType, this.mUserSerialNumber);
+ }
+
+ } // End builder
+
+ private LauncherUserInfo(@NonNull String userType, int userSerialNumber) {
+ this.mUserType = userType;
+ this.mUserSerialNumber = userSerialNumber;
+ }
+}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1b60f8ed904f..b15c9e4fa15b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1256,8 +1256,10 @@ public abstract class PackageManager {
public static final long MATCH_ARCHIVED_PACKAGES = 1L << 32;
/**
- * @hide
+ * Querying flag: always match components of packages in quarantined state.
+ * @see #isPackageQuarantined
*/
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
public static final long MATCH_QUARANTINED_COMPONENTS = 0x100000000L;
/**
@@ -9902,12 +9904,16 @@ public abstract class PackageManager {
/**
* Query if an app is currently quarantined.
+ * A misbehaving app can be quarantined by e.g. a system of another privileged entity.
+ * Quarantined apps are similar to disabled, but still visible in e.g. Launcher.
+ * Only activities of such apps can still be queried, but not services etc.
+ * Quarantined apps can't be bound to, and won't receive broadcasts.
+ * They can't be resolved, unless {@link #MATCH_QUARANTINED_COMPONENTS} specified.
*
* @return {@code true} if the given package is quarantined, {@code false} otherwise
* @throws NameNotFoundException if the package could not be found.
- *
- * @hide
*/
+ @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
public boolean isPackageQuarantined(@NonNull String packageName) throws NameNotFoundException {
throw new UnsupportedOperationException("isPackageQuarantined not implemented");
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index db12728cfb98..96609ad241bc 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -15,9 +15,9 @@ flag {
}
flag {
- name: "prevent_sdk_lib_app"
+ name: "disallow_sdk_libs_to_be_apps"
namespace: "package_manager_service"
- description: "Feature flag to enable the prevent sdk-library be an application."
+ description: "Feature flag to disallow a <sdk-library> to be an <application>."
bug: "295843617"
is_fixed_read_only: true
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 5cc3b92da305..c7790bd96c62 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@ import android.annotation.PluralsRes;
import android.annotation.RawRes;
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
+import android.app.LocaleConfig;
+import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -426,38 +428,59 @@ public class ResourcesImpl {
String[] selectedLocales = null;
String defaultLocale = null;
+ LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig();
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
- String[] availableLocales;
- // The LocaleList has changed. We must query the AssetManager's
- // available Locales and figure out the best matching Locale in the new
- // LocaleList.
- availableLocales = mAssets.getNonSystemLocales();
- if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- // No app defined locales, so grab the system locales.
- availableLocales = mAssets.getLocales();
+ if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+ Locale[] intersection =
+ locales.getIntersection(lc.getSupportedLocales());
+ mConfiguration.setLocales(new LocaleList(intersection));
+ selectedLocales = new String[intersection.length];
+ for (int i = 0; i < intersection.length; i++) {
+ selectedLocales[i] =
+ adjustLanguageTag(intersection[i].toLanguageTag());
+ }
+ defaultLocale =
+ adjustLanguageTag(lc.getDefaultLocale().toLanguageTag());
+ } else {
+ String[] availableLocales;
+ // The LocaleList has changed. We must query the AssetManager's
+ // available Locales and figure out the best matching Locale in the new
+ // LocaleList.
+ availableLocales = mAssets.getNonSystemLocales();
if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- availableLocales = null;
+ // No app defined locales, so grab the system locales.
+ availableLocales = mAssets.getLocales();
+ if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
+ availableLocales = null;
+ }
}
- }
- if (availableLocales != null) {
- final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
- availableLocales);
- if (bestLocale != null) {
- selectedLocales = new String[]{
- adjustLanguageTag(bestLocale.toLanguageTag())};
- if (!bestLocale.equals(locales.get(0))) {
- mConfiguration.setLocales(
- new LocaleList(bestLocale, locales));
+ if (availableLocales != null) {
+ final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+ availableLocales);
+ if (bestLocale != null) {
+ selectedLocales = new String[]{
+ adjustLanguageTag(bestLocale.toLanguageTag())};
+ if (!bestLocale.equals(locales.get(0))) {
+ mConfiguration.setLocales(
+ new LocaleList(bestLocale, locales));
+ }
}
}
}
}
}
if (selectedLocales == null) {
- selectedLocales = new String[]{
- adjustLanguageTag(locales.get(0).toLanguageTag())};
+ if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) {
+ selectedLocales = new String[locales.size()];
+ for (int i = 0; i < locales.size(); i++) {
+ selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+ }
+ } else {
+ selectedLocales = new String[]{
+ adjustLanguageTag(locales.get(0).toLanguageTag())};
+ }
}
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 0c2c0f494257..1b8eb0748737 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -8,3 +8,10 @@ flag {
# fixed_read_only or device wont boot because of permission issues accessing flags during boot
is_fixed_read_only: true
}
+
+flag {
+ name: "asset_file_descriptor_frro"
+ namespace: "resource_manager"
+ description: "Feature flag for passing in an AssetFileDescriptor to create an frro"
+ bug: "304478666"
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 20771af7d26d..524afe975d73 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -153,7 +153,7 @@ public final class CredentialManager {
mService.getCandidateCredentials(
request,
new GetCandidateCredentialsTransport(executor, callback),
- mContext.getOpPackageName());
+ callingPackage);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index b003e75b4e50..8a4f678b52f2 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -701,7 +701,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* }
* </pre>
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public void beginTransactionReadOnly() {
beginTransactionWithListenerReadOnly(null);
}
@@ -785,7 +785,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* }
* </pre>
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public void beginTransactionWithListenerReadOnly(
@Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, SQLiteSession.TRANSACTION_MODE_DEFERRED);
@@ -2224,7 +2224,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @throws IllegalStateException if a transaction is not in progress.
* @throws SQLiteException if the SQL cannot be compiled.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
@NonNull
public SQLiteRawStatement createRawStatement(@NonNull String sql) {
Objects.requireNonNull(sql);
@@ -2244,7 +2244,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @return The ROWID of the last row to be inserted under this connection.
* @throws IllegalStateException if there is no current transaction.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getLastInsertRowId() {
return getThreadSession().getLastInsertRowId();
}
@@ -2258,7 +2258,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @return The number of rows changed by the most recent sql statement
* @throws IllegalStateException if there is no current transaction.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getLastChangedRowCount() {
return getThreadSession().getLastChangedRowCount();
}
@@ -2286,7 +2286,7 @@ public final class SQLiteDatabase extends SQLiteClosable {
* @return The number of rows changed on the current connection.
* @throws IllegalStateException if there is no current transaction.
*/
- @FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+ @FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public long getTotalChangedRowCount() {
return getThreadSession().getTotalChangedRowCount();
}
diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java
index 827420f96fed..33f602bbd40e 100644
--- a/core/java/android/database/sqlite/SQLiteRawStatement.java
+++ b/core/java/android/database/sqlite/SQLiteRawStatement.java
@@ -71,7 +71,7 @@ import java.util.Objects;
*
* @see <a href="http://sqlite.org/c3ref/stmt.html">sqlite3_stmt</a>
*/
-@FlaggedApi(Flags.FLAG_SQLITE_APIS_15)
+@FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public final class SQLiteRawStatement implements Closeable {
private static final String TAG = "SQLiteRawStatement";
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 564df039456b..62a51236a2e2 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -1,7 +1,7 @@
package: "android.database.sqlite"
flag {
- name: "sqlite_apis_15"
+ name: "sqlite_apis_35"
namespace: "system_performance"
is_fixed_read_only: true
description: "SQLite APIs held back for Android 15"
diff --git a/core/java/android/hardware/HardwareBuffer.aidl b/core/java/android/hardware/HardwareBuffer.aidl
index 1333f0da725f..a9742cb6c084 100644
--- a/core/java/android/hardware/HardwareBuffer.aidl
+++ b/core/java/android/hardware/HardwareBuffer.aidl
@@ -16,4 +16,4 @@
package android.hardware;
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable HardwareBuffer ndk_header "android/hardware_buffer_aidl.h" rust_type "nativewindow::HardwareBuffer";
diff --git a/core/java/android/hardware/OverlayProperties.java b/core/java/android/hardware/OverlayProperties.java
index 8bfc2f7da25e..014cf6d86818 100644
--- a/core/java/android/hardware/OverlayProperties.java
+++ b/core/java/android/hardware/OverlayProperties.java
@@ -16,21 +16,28 @@
package android.hardware;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.hardware.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
import libcore.util.NativeAllocationRegistry;
/**
- * The class provides overlay properties of the device. OverlayProperties
- * exposes some capabilities from HWC e.g. if fp16 can be supported for HWUI.
+ * Provides supported overlay properties of the device.
*
- * In the future, more capabilities can be added, e.g., whether or not
- * per-layer colorspaces are supported.
- *
- * @hide
+ * <p>
+ * Hardware overlay is a technique to composite different buffers directly
+ * to the screen using display hardware rather than the GPU.
+ * The system compositor is able to assign any content managed by a
+ * {@link android.view.SurfaceControl} onto a hardware overlay if possible.
+ * Applications may be interested in the display hardware capabilities exposed
+ * by this class as a hint to determine if their {@link android.view.SurfaceControl}
+ * tree is power-efficient and performant.
+ * </p>
*/
+@FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public final class OverlayProperties implements Parcelable {
private static final NativeAllocationRegistry sRegistry =
@@ -38,10 +45,12 @@ public final class OverlayProperties implements Parcelable {
nGetDestructor());
private long mNativeObject;
+ // only for virtual displays
+ private static OverlayProperties sDefaultOverlayProperties;
// Invoked on destruction
private Runnable mCloser;
- public OverlayProperties(long nativeObject) {
+ private OverlayProperties(long nativeObject) {
if (nativeObject != 0) {
mCloser = sRegistry.registerNativeAllocation(this, nativeObject);
}
@@ -49,7 +58,20 @@ public final class OverlayProperties implements Parcelable {
}
/**
+ * For virtual displays, we provide an overlay properties object
+ * with RGBA 8888 only, sRGB only, true for mixed color spaces.
+ * @hide
+ */
+ public static OverlayProperties getDefault() {
+ if (sDefaultOverlayProperties == null) {
+ sDefaultOverlayProperties = new OverlayProperties(nCreateDefault());
+ }
+ return sDefaultOverlayProperties;
+ }
+
+ /**
* @return True if the device can support fp16, false otherwise.
+ * @hide
*/
public boolean supportFp16ForHdr() {
if (mNativeObject == 0) {
@@ -59,8 +81,13 @@ public final class OverlayProperties implements Parcelable {
}
/**
- * @return True if the device can support mixed colorspaces, false otherwise.
+ * Indicates that hw composition of two or more overlays
+ * with different colorspaces is supported on the device.
+ *
+ * @return True if the device can support mixed colorspaces efficiently,
+ * false if GPU composition fallback is otherwise required.
*/
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public boolean supportMixedColorSpaces() {
if (mNativeObject == 0) {
return false;
@@ -68,28 +95,14 @@ public final class OverlayProperties implements Parcelable {
return nSupportMixedColorSpaces(mNativeObject);
}
- /**
- * Release the local reference.
- */
- public void release() {
- if (mNativeObject != 0) {
- mCloser.run();
- mNativeObject = 0;
- }
- }
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
@Override
public int describeContents() {
return 0;
}
- /**
- * Flatten this object in to a Parcel.
- *
- * @param dest The Parcel in which the object should be written.
- * @param flags Additional flags about how the object should be written.
- * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
- */
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
if (mNativeObject == 0) {
@@ -100,6 +113,7 @@ public final class OverlayProperties implements Parcelable {
nWriteOverlayPropertiesToParcel(mNativeObject, dest);
}
+ @FlaggedApi(Flags.FLAG_OVERLAYPROPERTIES_CLASS_API)
public static final @NonNull Parcelable.Creator<OverlayProperties> CREATOR =
new Parcelable.Creator<OverlayProperties>() {
public OverlayProperties createFromParcel(Parcel in) {
@@ -115,6 +129,7 @@ public final class OverlayProperties implements Parcelable {
};
private static native long nGetDestructor();
+ private static native long nCreateDefault();
private static native boolean nSupportFp16ForHdr(long nativeObject);
private static native boolean nSupportMixedColorSpaces(long nativeObject);
private static native void nWriteOverlayPropertiesToParcel(long nativeObject, Parcel dest);
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 490ff640885e..7a43286692c4 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -805,7 +805,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* Get {@link Signature} object.
* @return {@link Signature} object or null if this doesn't contain one.
*/
- public Signature getSignature() {
+ public @Nullable Signature getSignature() {
return super.getSignature();
}
@@ -813,7 +813,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* Get {@link Cipher} object.
* @return {@link Cipher} object or null if this doesn't contain one.
*/
- public Cipher getCipher() {
+ public @Nullable Cipher getCipher() {
return super.getCipher();
}
@@ -821,7 +821,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* Get {@link Mac} object.
* @return {@link Mac} object or null if this doesn't contain one.
*/
- public Mac getMac() {
+ public @Nullable Mac getMac() {
return super.getMac();
}
diff --git a/core/java/android/hardware/biometrics/CryptoObject.java b/core/java/android/hardware/biometrics/CryptoObject.java
index 6ac1efb49839..39fbe83b6abb 100644
--- a/core/java/android/hardware/biometrics/CryptoObject.java
+++ b/core/java/android/hardware/biometrics/CryptoObject.java
@@ -20,6 +20,7 @@ import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OB
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.security.identity.IdentityCredential;
import android.security.identity.PresentationSession;
import android.security.keystore2.AndroidKeyStoreProvider;
@@ -33,20 +34,35 @@ import javax.crypto.Mac;
/**
* A wrapper class for the crypto objects supported by BiometricPrompt and FingerprintManager.
* Currently the framework supports {@link Signature}, {@link Cipher}, {@link Mac},
- * {@link IdentityCredential}, and {@link PresentationSession} objects.
+ * {@link KeyAgreement}, {@link IdentityCredential}, and {@link PresentationSession} objects.
* @hide
*/
public class CryptoObject {
private final Object mCrypto;
+ /**
+ * Create from a {@link Signature} object.
+ *
+ * @param signature a {@link Signature} object.
+ */
public CryptoObject(@NonNull Signature signature) {
mCrypto = signature;
}
+ /**
+ * Create from a {@link Cipher} object.
+ *
+ * @param cipher a {@link Cipher} object.
+ */
public CryptoObject(@NonNull Cipher cipher) {
mCrypto = cipher;
}
+ /**
+ * Create from a {@link Mac} object.
+ *
+ * @param mac a {@link Mac} object.
+ */
public CryptoObject(@NonNull Mac mac) {
mCrypto = mac;
}
@@ -62,10 +78,20 @@ public class CryptoObject {
mCrypto = credential;
}
+ /**
+ * Create from a {@link PresentationSession} object.
+ *
+ * @param session a {@link PresentationSession} object.
+ */
public CryptoObject(@NonNull PresentationSession session) {
mCrypto = session;
}
+ /**
+ * Create from a {@link KeyAgreement} object.
+ *
+ * @param keyAgreement a {@link KeyAgreement} object.
+ */
@FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
public CryptoObject(@NonNull KeyAgreement keyAgreement) {
mCrypto = keyAgreement;
@@ -75,7 +101,7 @@ public class CryptoObject {
* Get {@link Signature} object.
* @return {@link Signature} object or null if this doesn't contain one.
*/
- public Signature getSignature() {
+ public @Nullable Signature getSignature() {
return mCrypto instanceof Signature ? (Signature) mCrypto : null;
}
@@ -83,7 +109,7 @@ public class CryptoObject {
* Get {@link Cipher} object.
* @return {@link Cipher} object or null if this doesn't contain one.
*/
- public Cipher getCipher() {
+ public @Nullable Cipher getCipher() {
return mCrypto instanceof Cipher ? (Cipher) mCrypto : null;
}
@@ -91,7 +117,7 @@ public class CryptoObject {
* Get {@link Mac} object.
* @return {@link Mac} object or null if this doesn't contain one.
*/
- public Mac getMac() {
+ public @Nullable Mac getMac() {
return mCrypto instanceof Mac ? (Mac) mCrypto : null;
}
@@ -101,7 +127,7 @@ public class CryptoObject {
* @deprecated Use {@link PresentationSession} instead of {@link IdentityCredential}.
*/
@Deprecated
- public IdentityCredential getIdentityCredential() {
+ public @Nullable IdentityCredential getIdentityCredential() {
return mCrypto instanceof IdentityCredential ? (IdentityCredential) mCrypto : null;
}
@@ -109,16 +135,18 @@ public class CryptoObject {
* Get {@link PresentationSession} object.
* @return {@link PresentationSession} object or null if this doesn't contain one.
*/
- public PresentationSession getPresentationSession() {
+ public @Nullable PresentationSession getPresentationSession() {
return mCrypto instanceof PresentationSession ? (PresentationSession) mCrypto : null;
}
/**
- * Get {@link KeyAgreement} object.
+ * Get {@link KeyAgreement} object. A key-agreement protocol is a protocol whereby
+ * two or more parties can agree on a shared secret using public key cryptography.
+ *
* @return {@link KeyAgreement} object or null if this doesn't contain one.
*/
@FlaggedApi(FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT)
- public KeyAgreement getKeyAgreement() {
+ public @Nullable KeyAgreement getKeyAgreement() {
return mCrypto instanceof KeyAgreement ? (KeyAgreement) mCrypto : null;
}
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index bf77681bbbbd..db7055b1756d 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -357,7 +357,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
mCameraRepeatingSurface = mRepeatingRequestImageReader.getSurface();
}
mRepeatingRequestImageCallback = new CameraOutputImageCallback(
- mRepeatingRequestImageReader);
+ mRepeatingRequestImageReader, true /*pruneOlderBuffers*/);
mRepeatingRequestImageReader
.setOnImageAvailableListener(mRepeatingRequestImageCallback, mHandler);
}
@@ -398,7 +398,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
CameraExtensionCharacteristics.PROCESSING_INPUT_FORMAT);
}
- mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader);
+ mBurstCaptureImageCallback = new CameraOutputImageCallback(mBurstCaptureImageReader,
+ false /*pruneOlderBuffers*/);
mBurstCaptureImageReader.setOnImageAvailableListener(mBurstCaptureImageCallback,
mHandler);
mCameraBurstSurface = mBurstCaptureImageReader.getSurface();
@@ -1106,7 +1107,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
}
for (Pair<Image, TotalCaptureResult> captureStage : mCaptureStageMap.values()) {
- captureStage.first.close();
+ if (captureStage.first != null) {
+ captureStage.first.close();
+ }
}
mCaptureStageMap.clear();
}
@@ -1207,6 +1210,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
if (mImageProcessor != null) {
if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
Image img = mCapturePendingMap.get(timestamp).first;
+ mCapturePendingMap.remove(timestamp);
mCaptureStageMap.put(stageId, new Pair<>(img, result));
checkAndFireBurstProcessing();
} else {
@@ -1303,6 +1307,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
reader.detachImage(img);
if (mCapturePendingMap.indexOfKey(timestamp) >= 0) {
Integer stageId = mCapturePendingMap.get(timestamp).second;
+ mCapturePendingMap.remove(timestamp);
Pair<Image, TotalCaptureResult> captureStage =
mCaptureStageMap.get(stageId);
if (captureStage != null) {
@@ -1402,9 +1407,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
private HashMap<Long, Pair<Image, OnImageAvailableListener>> mImageListenerMap =
new HashMap<>();
private boolean mOutOfBuffers = false;
+ private final boolean mPruneOlderBuffers;
- CameraOutputImageCallback(ImageReader imageReader) {
+ CameraOutputImageCallback(ImageReader imageReader, boolean pruneOlderBuffers) {
mImageReader = imageReader;
+ mPruneOlderBuffers = pruneOlderBuffers;
}
@Override
@@ -1447,6 +1454,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession {
ArrayList<Long> removedTs = new ArrayList<>();
for (long ts : timestamps) {
if (ts < timestamp) {
+ if (!mPruneOlderBuffers) {
+ Log.w(TAG, "Unexpected older image with ts: " + ts);
+ continue;
+ }
Log.e(TAG, "Dropped image with ts: " + ts);
Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts);
if (entry.second != null) {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 8decd50664b3..2b5f5ee35a26 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -220,7 +220,7 @@ public final class DisplayManagerGlobal {
registerCallbackIfNeededLocked();
- if (DEBUG || extraLogging()) {
+ if (DEBUG) {
Log.d(TAG, "getDisplayInfo: displayId=" + displayId + ", info=" + info);
}
return info;
@@ -402,7 +402,7 @@ public final class DisplayManagerGlobal {
}
private void maybeLogAllDisplayListeners() {
- if (!sExtraDisplayListenerLogging) {
+ if (!extraLogging()) {
return;
}
@@ -1222,7 +1222,7 @@ public final class DisplayManagerGlobal {
private void handleMessage(Message msg) {
if (extraLogging()) {
- Slog.i(TAG, "DisplayListenerDelegate(" + eventToString(msg.what)
+ Slog.i(TAG, "DLD(" + eventToString(msg.what)
+ ", display=" + msg.arg1
+ ", mEventsMask=" + Long.toBinaryString(mEventsMask)
+ ", mPackageName=" + mPackageName
@@ -1231,9 +1231,10 @@ public final class DisplayManagerGlobal {
}
if (DEBUG) {
Trace.beginSection(
- "DisplayListenerDelegate(" + eventToString(msg.what)
+ TextUtils.trimToSize(
+ "DLD(" + eventToString(msg.what)
+ ", display=" + msg.arg1
- + ", listener=" + mListener.getClass() + ")");
+ + ", listener=" + mListener.getClass() + ")", 127));
}
switch (msg.what) {
case EVENT_DISPLAY_ADDED:
@@ -1422,11 +1423,12 @@ public final class DisplayManagerGlobal {
sExtraDisplayListenerLogging = !TextUtils.isEmpty(EXTRA_LOGGING_PACKAGE_NAME)
&& EXTRA_LOGGING_PACKAGE_NAME.equals(sCurrentPackageName);
}
- return sExtraDisplayListenerLogging;
+ // TODO: b/306170135 - return sExtraDisplayListenerLogging instead
+ return true;
}
private static boolean extraLogging() {
- return sExtraDisplayListenerLogging && EXTRA_LOGGING_PACKAGE_NAME.equals(
- sCurrentPackageName);
+ // TODO: b/306170135 - return sExtraDisplayListenerLogging & package name check instead
+ return true;
}
}
diff --git a/core/java/android/hardware/flags/overlayproperties_flags.aconfig b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
new file mode 100644
index 000000000000..c6a352e0fedf
--- /dev/null
+++ b/core/java/android/hardware/flags/overlayproperties_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.flags"
+
+flag {
+ name: "overlayproperties_class_api"
+ namespace: "core_graphics"
+ description: "public OverlayProperties class, OverlayProperties#supportMixedColorSpaces and Display#getOverlaySupport API"
+ bug: "267234573"
+}
diff --git a/core/java/android/hardware/hdmi/OWNERS b/core/java/android/hardware/hdmi/OWNERS
index 861e4409b014..6952e5d78d98 100644
--- a/core/java/android/hardware/hdmi/OWNERS
+++ b/core/java/android/hardware/hdmi/OWNERS
@@ -2,5 +2,4 @@
include /services/core/java/com/android/server/display/OWNERS
-marvinramin@google.com
-lcnathalie@google.com
+quxiangfang@google.com
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4c2bbc18f2db..ba80811e198c 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3363,6 +3363,13 @@ public class InputMethodService extends AbstractInputMethodService {
return true;
}
return false;
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
+ event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
+ if (mDecorViewVisible && mWindowVisible) {
+ int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
+ mPrivOps.switchKeyboardLayoutAsync(direction);
+ return true;
+ }
}
return doMovementKey(keyCode, event, MOVEMENT_DOWN);
}
diff --git a/core/java/android/nfc/Constants.java b/core/java/android/nfc/Constants.java
new file mode 100644
index 000000000000..f76833063605
--- /dev/null
+++ b/core/java/android/nfc/Constants.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.nfc;
+
+/**
+ * @hide
+ * TODO(b/303286040): Holds @hide API constants. Formalize these APIs.
+ */
+public final class Constants {
+ private Constants() { }
+
+ public static final String SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND = "nfc_payment_foreground";
+ public static final String SETTINGS_SECURE_NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
+ public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 46586308e3cf..4a7bd3f29458 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.app.Activity;
@@ -37,6 +38,7 @@ import android.nfc.tech.MifareClassic;
import android.nfc.tech.Ndef;
import android.nfc.tech.NfcA;
import android.nfc.tech.NfcF;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -1594,6 +1596,40 @@ public final class NfcAdapter {
mNfcActivityManager.disableReaderMode(activity);
}
+ // Flags arguments to NFC adapter to enable/disable NFC
+ private static final int DISABLE_POLLING_FLAGS = 0x1000;
+ private static final int ENABLE_POLLING_FLAGS = 0x0000;
+
+ /**
+ * Privileged API to enable disable reader polling.
+ * Note: Use with caution! The app is responsible for ensuring that the polling state is
+ * returned to normal.
+ *
+ * @see #enableReaderMode(Activity, ReaderCallback, int, Bundle) for more detailed
+ * documentation.
+ *
+ * @param enablePolling whether to enable or disable polling.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
+ @SuppressLint("VisiblySynchronized")
+ public void setReaderMode(boolean enablePolling) {
+ synchronized (NfcAdapter.class) {
+ if (!sHasNfcFeature) {
+ throw new UnsupportedOperationException();
+ }
+ }
+ Binder token = new Binder();
+ int flags = enablePolling ? ENABLE_POLLING_FLAGS : DISABLE_POLLING_FLAGS;
+ try {
+ NfcAdapter.sService.setReaderMode(token, null, flags, null);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
+
/**
* Manually invoke Android Beam to share data.
*
diff --git a/core/java/android/nfc/cardemulation/AidGroup.java b/core/java/android/nfc/cardemulation/AidGroup.java
index 958669ee5852..ae3e333051d7 100644
--- a/core/java/android/nfc/cardemulation/AidGroup.java
+++ b/core/java/android/nfc/cardemulation/AidGroup.java
@@ -34,6 +34,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Pattern;
/**********************************************************************
* This file is not a part of the NFC mainline module *
@@ -79,7 +80,7 @@ public final class AidGroup implements Parcelable {
throw new IllegalArgumentException("Too many AIDs in AID group.");
}
for (String aid : aids) {
- if (!CardEmulation.isValidAid(aid)) {
+ if (!isValidAid(aid)) {
throw new IllegalArgumentException("AID " + aid + " is not a valid AID.");
}
}
@@ -264,4 +265,34 @@ public final class AidGroup implements Parcelable {
return CardEmulation.CATEGORY_PAYMENT.equals(category) ||
CardEmulation.CATEGORY_OTHER.equals(category);
}
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index 18ec914860fb..665b7531d3ce 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
/**
* Class holding APDU service info.
@@ -307,7 +308,7 @@ public final class ApduServiceInfo implements Parcelable {
com.android.internal.R.styleable.AidFilter);
String aid = a.getString(com.android.internal.R.styleable.AidFilter_name).
toUpperCase();
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -321,7 +322,7 @@ public final class ApduServiceInfo implements Parcelable {
toUpperCase();
// Add wildcard char to indicate prefix
aid = aid.concat("*");
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -335,7 +336,7 @@ public final class ApduServiceInfo implements Parcelable {
toUpperCase();
// Add wildcard char to indicate suffix
aid = aid.concat("#");
- if (CardEmulation.isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
+ if (isValidAid(aid) && !currentGroup.getAids().contains(aid)) {
currentGroup.getAids().add(aid);
} else {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
@@ -806,7 +807,7 @@ public final class ApduServiceInfo implements Parcelable {
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
public void dumpDebug(@NonNull ProtoOutputStream proto) {
- Utils.dumpDebugComponentName(getComponent(), proto, ApduServiceInfoProto.COMPONENT_NAME);
+ getComponent().dumpDebug(proto, ApduServiceInfoProto.COMPONENT_NAME);
proto.write(ApduServiceInfoProto.DESCRIPTION, getDescription());
proto.write(ApduServiceInfoProto.ON_HOST, mOnHost);
if (!mOnHost) {
@@ -825,4 +826,34 @@ public final class ApduServiceInfo implements Parcelable {
}
proto.write(ApduServiceInfoProto.SETTINGS_ACTIVITY_NAME, mSettingsActivityName);
}
+
+ private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ /**
+ * Copied over from {@link CardEmulation#isValidAid(String)}
+ * @hide
+ */
+ private static boolean isValidAid(String aid) {
+ if (aid == null)
+ return false;
+
+ // If a prefix/subset AID, the total length must be odd (even # of AID chars + '*')
+ if ((aid.endsWith("*") || aid.endsWith("#")) && ((aid.length() % 2) == 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // If not a prefix/subset AID, the total length must be even (even # of AID chars)
+ if ((!(aid.endsWith("*") || aid.endsWith("#"))) && ((aid.length() % 2) != 0)) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ // Verify hex characters
+ if (!AID_PATTERN.matcher(aid).matches()) {
+ Log.e(TAG, "AID " + aid + " is not a valid AID.");
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 4909b0830eeb..32c2a1b40530 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -26,6 +26,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.nfc.Constants;
import android.nfc.INfcCardEmulation;
import android.nfc.NfcAdapter;
import android.os.RemoteException;
@@ -274,7 +275,7 @@ public final class CardEmulation {
try {
preferForeground = Settings.Secure.getInt(
contextAsUser.getContentResolver(),
- Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0;
+ Constants.SETTINGS_SECURE_NFC_PAYMENT_FOREGROUND) != 0;
} catch (SettingNotFoundException e) {
}
return preferForeground;
diff --git a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
index ec919e4d66bc..33bc16978721 100644
--- a/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/NfcFServiceInfo.java
@@ -173,7 +173,7 @@ public final class NfcFServiceInfo implements Parcelable {
com.android.internal.R.styleable.SystemCodeFilter);
systemCode = a.getString(
com.android.internal.R.styleable.SystemCodeFilter_name).toUpperCase();
- if (!NfcFCardEmulation.isValidSystemCode(systemCode) &&
+ if (!isValidSystemCode(systemCode) &&
!systemCode.equalsIgnoreCase("NULL")) {
Log.e(TAG, "Invalid System Code: " + systemCode);
systemCode = null;
@@ -187,7 +187,7 @@ public final class NfcFServiceInfo implements Parcelable {
com.android.internal.R.styleable.Nfcid2Filter_name).toUpperCase();
if (!nfcid2.equalsIgnoreCase("RANDOM") &&
!nfcid2.equalsIgnoreCase("NULL") &&
- !NfcFCardEmulation.isValidNfcid2(nfcid2)) {
+ !isValidNfcid2(nfcid2)) {
Log.e(TAG, "Invalid NFCID2: " + nfcid2);
nfcid2 = null;
}
@@ -436,10 +436,62 @@ public final class NfcFServiceInfo implements Parcelable {
*/
@FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
public void dumpDebug(@NonNull ProtoOutputStream proto) {
- Utils.dumpDebugComponentName(getComponent(), proto, NfcFServiceInfoProto.COMPONENT_NAME);
+ getComponent().dumpDebug(proto, NfcFServiceInfoProto.COMPONENT_NAME);
proto.write(NfcFServiceInfoProto.DESCRIPTION, getDescription());
proto.write(NfcFServiceInfoProto.SYSTEM_CODE, getSystemCode());
proto.write(NfcFServiceInfoProto.NFCID2, getNfcid2());
proto.write(NfcFServiceInfoProto.T3T_PMM, getT3tPmm());
}
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidSystemCode(String)}
+ * @hide
+ */
+ private static boolean isValidSystemCode(String systemCode) {
+ if (systemCode == null) {
+ return false;
+ }
+ if (systemCode.length() != 4) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ // check if the value is between "4000" and "4FFF" (excluding "4*FF")
+ if (!systemCode.startsWith("4") || systemCode.toUpperCase().endsWith("FF")) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ try {
+ Integer.parseInt(systemCode, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "System Code " + systemCode + " is not a valid System Code.");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Copied over from {@link NfcFCardEmulation#isValidNfcid2(String)}
+ * @hide
+ */
+ private static boolean isValidNfcid2(String nfcid2) {
+ if (nfcid2 == null) {
+ return false;
+ }
+ if (nfcid2.length() != 16) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ // check if the the value starts with "02FE"
+ if (!nfcid2.toUpperCase().startsWith("02FE")) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ try {
+ Long.parseLong(nfcid2, 16);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "NFCID2 " + nfcid2 + " is not a valid NFCID2.");
+ return false;
+ }
+ return true;
+ }
}
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index 55b0b4261763..cd50ace036de 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -13,3 +13,10 @@ flag {
description: "Flag for NFC reader option API changes"
bug: "291187960"
}
+
+flag {
+ name: "enable_nfc_user_restriction"
+ namespace: "nfc"
+ description: "Flag for NFC user restriction"
+ bug: "291187960"
+}
diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index 47ad72fe8d0e..e8ad303c4208 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
@@ -133,6 +134,7 @@ public final class BugreportParams {
* The maximum value of supported bugreport mode.
* @hide
*/
+ @FlaggedApi(android.os.Flags.FLAG_BUGREPORT_MODE_MAX_VALUE)
@TestApi
public static final int BUGREPORT_MODE_MAX_VALUE = BUGREPORT_MODE_ONBOARDING;
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd280a0f3..d7e440b66e13 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@ public final class LocaleList implements Parcelable {
/**
* Find the intersection between this LocaleList and another
- * @return a String array of the Locales in both LocaleLists
+ * @return an array of the Locales in both LocaleLists
* {@hide}
*/
@NonNull
- public String[] getIntersection(@NonNull LocaleList other) {
- List<String> intersection = new ArrayList<>();
+ public Locale[] getIntersection(@NonNull LocaleList other) {
+ List<Locale> intersection = new ArrayList<>();
for (Locale l1 : mList) {
for (Locale l2 : other.mList) {
if (matchesLanguageAndScript(l2, l1)) {
- intersection.add(l1.toLanguageTag());
+ intersection.add(l1);
break;
}
}
}
- return intersection.toArray(new String[0]);
+ return intersection.toArray(new Locale[0]);
}
/**
diff --git a/core/java/android/os/OomKillRecord.java b/core/java/android/os/OomKillRecord.java
index 151a65fdfaf5..ca1d49a93def 100644
--- a/core/java/android/os/OomKillRecord.java
+++ b/core/java/android/os/OomKillRecord.java
@@ -15,10 +15,15 @@
*/
package android.os;
+import com.android.internal.util.FrameworkStatsLog;
/**
+ * Activity manager communication with kernel out-of-memory (OOM) data handling
+ * and statsd atom logging.
+ *
* Expected data to get back from the OOM event's file.
- * Note that this should be equivalent to the struct <b>OomKill</b> inside
+ * Note that this class fields' should be equivalent to the struct
+ * <b>OomKill</b> inside
* <pre>
* system/memory/libmeminfo/libmemevents/include/memevents.h
* </pre>
@@ -41,6 +46,18 @@ public final class OomKillRecord {
this.mOomScoreAdj = oomScoreAdj;
}
+ /**
+ * Logs the event when the kernel OOM killer claims a victims to reduce
+ * memory pressure.
+ * KernelOomKillOccurred = 754
+ */
+ public void logKillOccurred() {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.KERNEL_OOM_KILL_OCCURRED,
+ mUid, mPid, mOomScoreAdj, mTimeStampInMillis,
+ mProcessName);
+ }
+
public long getTimestampMilli() {
return mTimeStampInMillis;
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 4c8ef97a7437..72bc2113f93f 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -23,6 +23,7 @@ import android.Manifest;
import android.accounts.AccountManager;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,6 +59,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
+import android.nfc.Flags;
import android.provider.Settings;
import android.util.AndroidException;
import android.util.ArraySet;
@@ -1871,6 +1873,7 @@ public class UserManager {
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
+ @FlaggedApi(Flags.FLAG_ENABLE_NFC_USER_RESTRICTION)
public static final String DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO =
"no_near_field_communication_radio";
@@ -3330,7 +3333,10 @@ public class UserManager {
*
* @return whether the context user is running in the foreground.
*/
- @UserHandleAware
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public boolean isUserForeground() {
try {
return mService.isUserForeground(mUserId);
@@ -3404,11 +3410,10 @@ public class UserManager {
* @hide
*/
@SystemApi
- @UserHandleAware
- @RequiresPermission(anyOf = {
- "android.permission.INTERACT_ACROSS_USERS",
- "android.permission.MANAGE_USERS"
- })
+ @UserHandleAware(
+ requiresAnyOfPermissionsIfNotCaller = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
public boolean isUserVisible() {
try {
return mService.isUserVisible(mUserId);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index b7f2e065bac8..c4521c036329 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -27,3 +27,10 @@ flag {
description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
bug: "299069460"
}
+
+flag {
+ name: "bugreport_mode_max_value"
+ namespace: "telephony"
+ description: "Introduce a constant as maximum value of bugreport mode."
+ bug: "305067125"
+}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index a39157103f71..27ad45de69e6 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -3196,6 +3196,16 @@ public final class Telephony {
public static final String ALWAYS_ON = "always_on";
/**
+ * The infrastructure bitmask which the APN can be used on. For example, some APNs can only
+ * be used when the device is on cellular, on satellite, or both. The default value is
+ * 1 (INFRASTRUCTURE_CELLULAR).
+ *
+ * <P>Type: INTEGER</P>
+ * @hide
+ */
+ public static final String INFRASTRUCTURE_BITMASK = "infrastructure_bitmask";
+
+ /**
* MVNO type:
* {@code SPN (Service Provider Name), IMSI, GID (Group Identifier Level 1)}.
* <P>Type: TEXT</P>
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 800149cb0a67..94eca3d16187 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -12,6 +12,7 @@ flag {
namespace: "hardware_backed_security"
description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
bug: "296464083"
+ is_fixed_read_only: true
}
flag {
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index a29bf7a06334..1afe8d95185b 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -1401,6 +1401,7 @@ public final class Dataset implements Parcelable {
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
parcel.writeInt(mEligibleReason);
+ parcel.writeTypedObject(mAuthenticationExtras, flags);
}
public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@@ -1436,6 +1437,7 @@ public final class Dataset implements Parcelable {
android.content.IntentSender.class);
final String datasetId = parcel.readString();
final int eligibleReason = parcel.readInt();
+ final Bundle authenticationExtras = parcel.readTypedObject(Bundle.CREATOR);
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
@@ -1480,6 +1482,7 @@ public final class Dataset implements Parcelable {
fieldDialogPresentation);
}
builder.setAuthentication(authentication);
+ builder.setAuthenticationExtras(authenticationExtras);
builder.setId(datasetId);
Dataset dataset = builder.build();
dataset.mEligibleReason = eligibleReason;
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c82a4cabaddd..2a4cbaf79a75 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -28,8 +28,6 @@ import android.system.OsConstants;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -59,8 +57,7 @@ public class NotificationRankingUpdate implements Parcelable {
* @hide
*/
public NotificationRankingUpdate(Parcel in) {
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
// Recover the ranking map from the SharedMemory and store it in mapParcel.
final Parcel mapParcel = Parcel.obtain();
ByteBuffer buffer = null;
@@ -176,8 +173,7 @@ public class NotificationRankingUpdate implements Parcelable {
*/
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
final Parcel mapParcel = Parcel.obtain();
ArrayList<NotificationListenerService.Ranking> marshalableRankings = new ArrayList<>();
Bundle smartActionsBundle = new Bundle();
diff --git a/core/java/android/service/notification/flags.aconfig b/core/java/android/service/notification/flags.aconfig
new file mode 100644
index 000000000000..293143595b5e
--- /dev/null
+++ b/core/java/android/service/notification/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.service.notification"
+
+flag {
+ name: "ranking_update_ashmem"
+ namespace: "systemui"
+ description: "This flag controls moving ranking update contents into ashmem"
+ bug: "284297289"
+}
+
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
index 5b57fc7fa528..dce874dcd982 100644
--- a/core/java/android/service/rotationresolver/OWNERS
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -1,9 +1,7 @@
# Bug component: 814982
asalo@google.com
-augale@google.com
eejiang@google.com
payamp@google.com
siddikap@google.com
-svetoslavganov@google.com
tgadh@google.com
diff --git a/core/java/android/service/voice/HotwordTrainingAudio.java b/core/java/android/service/voice/HotwordTrainingAudio.java
index 91e34dcc4a64..916fa36b2339 100644
--- a/core/java/android/service/voice/HotwordTrainingAudio.java
+++ b/core/java/android/service/voice/HotwordTrainingAudio.java
@@ -16,6 +16,7 @@
package android.service.voice;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -23,6 +24,7 @@ import android.annotation.SystemApi;
import android.media.AudioFormat;
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.voice.flags.Flags;
import com.android.internal.util.DataClass;
@@ -33,6 +35,7 @@ import java.util.Objects;
*
* @hide
*/
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@DataClass(
genConstructor = false,
genBuilder = true,
@@ -65,12 +68,12 @@ public final class HotwordTrainingAudio implements Parcelable {
/**
* App-defined identifier to distinguish hotword training audio instances.
- */
+ * <p> Returns -1 if unset. */
@NonNull
private final int mAudioType;
private static int defaultAudioType() {
- return 0;
+ return -1;
}
/**
@@ -152,6 +155,7 @@ public final class HotwordTrainingAudio implements Parcelable {
/**
* App-defined identifier to distinguish hotword training audio instances.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull int getAudioType() {
@@ -274,6 +278,7 @@ public final class HotwordTrainingAudio implements Parcelable {
/**
* A builder for {@link HotwordTrainingAudio}
*/
+ @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static final class Builder extends BaseBuilder {
@@ -318,6 +323,7 @@ public final class HotwordTrainingAudio implements Parcelable {
/**
* App-defined identifier to distinguish hotword training audio instances.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull Builder setAudioType(@NonNull int value) {
@@ -368,7 +374,7 @@ public final class HotwordTrainingAudio implements Parcelable {
}
@DataClass.Generated(
- time = 1694193905346L,
+ time = 1697827049629L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingAudio.java",
inputSignatures = "public static final int HOTWORD_OFFSET_UNSET\nprivate final @android.annotation.NonNull byte[] mHotwordAudio\nprivate final @android.annotation.NonNull android.media.AudioFormat mAudioFormat\nprivate final @android.annotation.NonNull int mAudioType\nprivate int mHotwordOffsetMillis\nprivate java.lang.String hotwordAudioToString()\nprivate static int defaultAudioType()\nclass HotwordTrainingAudio extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.service.voice.HotwordTrainingAudio.Builder setHotwordAudio(byte[])\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/service/voice/HotwordTrainingData.java b/core/java/android/service/voice/HotwordTrainingData.java
index 31aeb9ca219e..aa6dab3629b4 100644
--- a/core/java/android/service/voice/HotwordTrainingData.java
+++ b/core/java/android/service/voice/HotwordTrainingData.java
@@ -16,10 +16,12 @@
package android.service.voice;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.service.voice.flags.Flags;
import android.text.TextUtils;
import com.android.internal.util.DataClass;
@@ -47,6 +49,7 @@ import java.util.List;
genParcelable = true,
genToString = true)
@SystemApi
+@FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
public final class HotwordTrainingData implements Parcelable {
/** Max size for hotword training data in bytes. */
public static int getMaxTrainingDataBytes() {
@@ -63,11 +66,11 @@ public final class HotwordTrainingData implements Parcelable {
}
/** App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset. */
+ * <p> Returns -1 if unset. */
private final int mTimeoutStage;
private static int defaultTimeoutStage() {
- return 0;
+ return -1;
}
private void onConstructed() {
@@ -120,7 +123,7 @@ public final class HotwordTrainingData implements Parcelable {
/**
* App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public int getTimeoutStage() {
@@ -218,6 +221,7 @@ public final class HotwordTrainingData implements Parcelable {
/**
* A builder for {@link HotwordTrainingData}
*/
+ @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
@SuppressWarnings("WeakerAccess")
@DataClass.Generated.Member
public static final class Builder {
@@ -251,7 +255,7 @@ public final class HotwordTrainingData implements Parcelable {
/**
* App-defined stage when hotword model timed-out while running.
- * <p> Returns 0 if unset.
+ * <p> Returns -1 if unset.
*/
@DataClass.Generated.Member
public @NonNull Builder setTimeoutStage(int value) {
@@ -287,7 +291,7 @@ public final class HotwordTrainingData implements Parcelable {
}
@DataClass.Generated(
- time = 1696092128091L,
+ time = 1697826948280L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/service/voice/HotwordTrainingData.java",
inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"trainingAudio\") java.util.List<android.service.voice.HotwordTrainingAudio> mTrainingAudioList\nprivate final int mTimeoutStage\npublic static int getMaxTrainingDataBytes()\nprivate static java.util.List<android.service.voice.HotwordTrainingAudio> defaultTrainingAudioList()\nprivate static int defaultTimeoutStage()\nprivate void onConstructed()\nclass HotwordTrainingData extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 65a1da6b81b8..4c8188801eff 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -190,7 +190,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
boolean useFallbackLineSpacing) {
return replaceOrMake(source, paint, outerWidth, align, 1.0f, 0.0f, metrics, includePad,
- ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */);
+ ellipsize, ellipsizedWidth, useFallbackLineSpacing, false /* useBoundsForWidth */,
+ null /* minimumFontMetrics */);
}
/** @hide */
@@ -199,7 +200,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
@NonNull Alignment align, float spacingMultiplier, float spacingAmount,
@NonNull BoringLayout.Metrics metrics, boolean includePad,
@Nullable TextUtils.TruncateAt ellipsize, @IntRange(from = 0) int ellipsizedWidth,
- boolean useFallbackLineSpacing, boolean useBoundsForWidth) {
+ boolean useFallbackLineSpacing, boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
boolean trust;
if (ellipsize == null || ellipsize == TextUtils.TruncateAt.MARQUEE) {
@@ -270,7 +272,8 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
spacingAdd, includePad, false /* fallbackLineSpacing */,
outerwidth /* ellipsizedWidth */, null /* ellipsize */, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
- null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ null /* rightIndents */, JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false,
+ null);
mEllipsizedWidth = outerwidth;
mEllipsizedStart = 0;
@@ -343,7 +346,7 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
ellipsizedWidth, ellipsize, 1 /* maxLines */,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null /* leftIndents */,
null /* rightIndents */, JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */);
+ LineBreakConfig.NONE, metrics, false /* useBoundsForWidth */, null);
}
/** @hide */
@@ -359,12 +362,13 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
int ellipsizedWidth,
TextUtils.TruncateAt ellipsize,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
this(text, paint, width, align, TextDirectionHeuristics.LTR,
spacingMult, spacingAdd, includePad, fallbackLineSpacing, ellipsizedWidth,
ellipsize, 1 /* maxLines */, Layout.BREAK_STRATEGY_SIMPLE,
Layout.HYPHENATION_FREQUENCY_NONE, null, null, Layout.JUSTIFICATION_MODE_NONE,
- LineBreakConfig.NONE, metrics, useBoundsForWidth);
+ LineBreakConfig.NONE, metrics, useBoundsForWidth, minimumFontMetrics);
}
/* package */ BoringLayout(
@@ -387,12 +391,13 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
int justificationMode,
LineBreakConfig lineBreakConfig,
Metrics metrics,
- boolean useBoundsForWidth) {
+ boolean useBoundsForWidth,
+ @Nullable Paint.FontMetrics minimumFontMetrics) {
super(text, paint, width, align, textDir, spacingMult, spacingAdd, includePad,
fallbackLineSpacing, ellipsizedWidth, ellipsize, maxLines, breakStrategy,
hyphenationFrequency, leftIndents, rightIndents, justificationMode,
- lineBreakConfig, useBoundsForWidth);
+ lineBreakConfig, useBoundsForWidth, minimumFontMetrics);
boolean trust;
@@ -548,6 +553,15 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
@NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
@Nullable Metrics metrics) {
+ return isBoring(text, paint, textDir, useFallbackLineSpacing, null, metrics);
+ }
+
+ /**
+ * @hide
+ */
+ public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
+ @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
+ @Nullable Paint.FontMetrics minimumFontMetrics, @Nullable Metrics metrics) {
final int textLength = text.length();
if (hasAnyInterestingChars(text, textLength)) {
return null; // There are some interesting characters. Not boring.
@@ -570,6 +584,19 @@ public class BoringLayout extends Layout implements TextUtils.EllipsizeCallback
fm.reset();
}
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (minimumFontMetrics == null) {
+ paint.getFontMetricsIntForLocale(fm);
+ } else {
+ fm.set(minimumFontMetrics);
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than
+ // descent.
+ fm.top = Math.min(fm.top, fm.ascent);
+ fm.bottom = Math.max(fm.bottom, fm.descent);
+ }
+ }
+
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null,
diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java
index 46fa5017106b..0421d5aaa69b 100644
--- a/core/java/android/text/ClientFlags.java
+++ b/core/java/android/text/ClientFlags.java
@@ -27,14 +27,6 @@ import com.android.text.flags.Flags;
* @hide
*/
public class ClientFlags {
-
- /**
- * @see Flags#deprecateFontsXml()
- */
- public static boolean deprecateFontsXml() {
- return TextFlags.isFeatureEnabled(Flags.FLAG_DEPRECATE_FONTS_XML);
- }
-
/**
* @see Flags#noBreakNoHyphenationSpan()
*/
@@ -55,4 +47,11 @@ public class ClientFlags {
public static boolean useBoundsForWidth() {
return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH);
}
+
+ /**
+ * @see Flags#fixLineHeightForLocale()
+ */
+ public static boolean fixLineHeightForLocale() {
+ return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE);
+ }
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index a0cd0748f509..7b9cb6afd6a0 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
@@ -315,6 +316,43 @@ public class DynamicLayout extends Layout {
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link DynamicLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this method.
@@ -347,6 +385,7 @@ public class DynamicLayout extends Layout {
private int mEllipsizedWidth;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -422,7 +461,7 @@ public class DynamicLayout extends Layout {
false /* fallbackLineSpacing */, ellipsizedWidth, ellipsize,
Integer.MAX_VALUE /* maxLines */, breakStrategy, hyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, justificationMode,
- lineBreakConfig, false /* useBoundsForWidth */);
+ lineBreakConfig, false /* useBoundsForWidth */, null /* minimumFontMetrics */);
final Builder b = Builder.obtain(base, paint, width)
.setAlignment(align)
@@ -448,7 +487,7 @@ public class DynamicLayout extends Layout {
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
Integer.MAX_VALUE /* maxLines */, b.mBreakStrategy, b.mHyphenationFrequency,
null /* leftIndents */, null /* rightIndents */, b.mJustificationMode,
- b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mLineBreakConfig, b.mUseBoundsForWidth, b.mMinimumFontMetrics);
mDisplay = b.mDisplay;
mIncludePad = b.mIncludePad;
@@ -476,6 +515,7 @@ public class DynamicLayout extends Layout {
mBase = b.mBase;
mFallbackLineSpacing = b.mFallbackLineSpacing;
mUseBoundsForWidth = b.mUseBoundsForWidth;
+ mMinimumFontMetrics = b.mMinimumFontMetrics;
if (b.mEllipsize != null) {
mInts = new PackedIntVector(COLUMNS_ELLIPSIZE);
mEllipsizedWidth = b.mEllipsizedWidth;
@@ -672,6 +712,7 @@ public class DynamicLayout extends Layout {
.setAddLastLineLineSpacing(!islast)
.setIncludePad(false)
.setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics)
.setCalculateBounds(true);
reflowed = b.buildPartialStaticLayoutForDynamicLayout(true /* trackpadding */, reflowed);
@@ -1324,6 +1365,7 @@ public class DynamicLayout extends Layout {
private Rect mTempRect = new Rect();
private boolean mUseBoundsForWidth;
+ @Nullable Paint.FontMetrics mMinimumFontMetrics;
@UnsupportedAppUsage
private static StaticLayout sStaticLayout = null;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 4f4dea780171..47c29d968558 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -287,7 +288,7 @@ public abstract class Layout {
this(text, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
spacingMult, spacingAdd, false, false, 0, null, Integer.MAX_VALUE,
BREAK_STRATEGY_SIMPLE, HYPHENATION_FREQUENCY_NONE, null, null,
- JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false);
+ JUSTIFICATION_MODE_NONE, LineBreakConfig.NONE, false, null);
}
/**
@@ -336,7 +337,8 @@ public abstract class Layout {
int[] rightIndents,
int justificationMode,
LineBreakConfig lineBreakConfig,
- boolean useBoundsForWidth
+ boolean useBoundsForWidth,
+ Paint.FontMetrics minimumFontMetrics
) {
if (width < 0)
@@ -371,6 +373,7 @@ public abstract class Layout {
mJustificationMode = justificationMode;
mLineBreakConfig = lineBreakConfig;
mUseBoundsForWidth = useBoundsForWidth;
+ mMinimumFontMetrics = minimumFontMetrics;
}
/**
@@ -3332,6 +3335,7 @@ public abstract class Layout {
private int mJustificationMode;
private LineBreakConfig mLineBreakConfig;
private boolean mUseBoundsForWidth;
+ private @Nullable Paint.FontMetrics mMinimumFontMetrics;
/** @hide */
@IntDef(prefix = { "DIR_" }, value = {
@@ -3787,12 +3791,48 @@ public abstract class Layout {
return this;
}
+ /**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left it as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
private BoringLayout.Metrics isBoring() {
if (mStart != 0 || mEnd != mText.length()) { // BoringLayout only support entire text.
return null;
}
BoringLayout.Metrics metrics = BoringLayout.isBoring(mText, mPaint, mTextDir,
- mFallbackLineSpacing, null);
+ mFallbackLineSpacing, mMinimumFontMetrics, null);
if (metrics == null) {
return null;
}
@@ -3833,7 +3873,8 @@ public abstract class Layout {
mText, mPaint, mWidth, mAlignment, mTextDir, mSpacingMult, mSpacingAdd,
mIncludePad, mFallbackLineSpacing, mEllipsizedWidth, mEllipsize, mMaxLines,
mBreakStrategy, mHyphenationFrequency, mLeftIndents, mRightIndents,
- mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth);
+ mJustificationMode, mLineBreakConfig, metrics, mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
@@ -3858,6 +3899,7 @@ public abstract class Layout {
private int mJustificationMode = JUSTIFICATION_MODE_NONE;
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
+ private Paint.FontMetrics mMinimumFontMetrics;
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -4164,4 +4206,22 @@ public abstract class Layout {
public boolean getUseBoundsForWidth() {
return mUseBoundsForWidth;
}
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 01279cea073f..77e616b358cb 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -16,6 +16,7 @@
package android.text;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.annotation.FlaggedApi;
@@ -460,6 +461,43 @@ public class StaticLayout extends Layout {
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the
+ * font metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is
+ * used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin
+ * script. By setting the metrics obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} for Japanese or leave it
+ * {@code null} if the Paint's locale is Japanese, the line spacing for Japanese is reserved
+ * if the text is an English text. If the vertical metrics of the text is larger than
+ * Japanese, for example Burmese, the bigger font metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the
+ * value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see android.widget.TextView#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see android.widget.TextView#getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @NonNull
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ return this;
+ }
+
+ /**
* Build the {@link StaticLayout} after options have been set.
*
* <p>Note: the builder object must not be reused in any way after calling this
@@ -520,6 +558,7 @@ public class StaticLayout extends Layout {
private LineBreakConfig mLineBreakConfig = LineBreakConfig.NONE;
private boolean mUseBoundsForWidth;
private boolean mCalculateBounds;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -550,7 +589,8 @@ public class StaticLayout extends Layout {
null, // rightIndents
JUSTIFICATION_MODE_NONE,
null, // lineBreakConfig,
- false // useBoundsForWidth
+ false, // useBoundsForWidth
+ null // minimumFontMetrics
);
mColumns = COLUMNS_ELLIPSIZE;
@@ -627,7 +667,8 @@ public class StaticLayout extends Layout {
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
- b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth);
+ b.mRightIndents, b.mJustificationMode, b.mLineBreakConfig, b.mUseBoundsForWidth,
+ b.mMinimumFontMetrics);
mColumns = columnSize;
if (b.mEllipsize != null) {
@@ -711,6 +752,35 @@ public class StaticLayout extends Layout {
indents = null;
}
+ int defaultTop;
+ int defaultAscent;
+ int defaultDescent;
+ int defaultBottom;
+ if (ClientFlags.fixLineHeightForLocale()) {
+ if (b.mMinimumFontMetrics != null) {
+ defaultTop = (int) Math.floor(b.mMinimumFontMetrics.top);
+ defaultAscent = Math.round(b.mMinimumFontMetrics.ascent);
+ defaultDescent = Math.round(b.mMinimumFontMetrics.descent);
+ defaultBottom = (int) Math.ceil(b.mMinimumFontMetrics.bottom);
+ } else {
+ paint.getFontMetricsIntForLocale(fm);
+ defaultTop = fm.top;
+ defaultAscent = fm.ascent;
+ defaultDescent = fm.descent;
+ defaultBottom = fm.bottom;
+ }
+
+ // Because the font metrics is provided by public APIs, adjust the top/bottom with
+ // ascent/descent: top must be smaller than ascent, bottom must be larger than descent.
+ defaultTop = Math.min(defaultTop, defaultAscent);
+ defaultBottom = Math.max(defaultBottom, defaultDescent);
+ } else {
+ defaultTop = 0;
+ defaultAscent = 0;
+ defaultDescent = 0;
+ defaultBottom = 0;
+ }
+
final LineBreaker lineBreaker = new LineBreaker.Builder()
.setBreakStrategy(b.mBreakStrategy)
.setHyphenationFrequency(getBaseHyphenationFrequency(b.mHyphenationFrequency))
@@ -889,7 +959,10 @@ public class StaticLayout extends Layout {
// measuring
int here = paraStart;
- int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmTop = defaultTop;
+ int fmBottom = defaultBottom;
+ int fmAscent = defaultAscent;
+ int fmDescent = defaultDescent;
int fmCacheIndex = 0;
int spanEndCacheIndex = 0;
int breakIndex = 0;
@@ -982,7 +1055,15 @@ public class StaticLayout extends Layout {
&& mLineCount < mMaximumVisibleLineCount) {
final MeasuredParagraph measuredPara =
MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
- paint.getFontMetricsInt(fm);
+ if (ClientFlags.fixLineHeightForLocale()) {
+ fm.top = defaultTop;
+ fm.ascent = defaultAscent;
+ fm.descent = defaultDescent;
+ fm.bottom = defaultBottom;
+ } else {
+ paint.getFontMetricsInt(fm);
+ }
+
v = out(source,
bufEnd, bufEnd, fm.ascent, fm.descent,
fm.top, fm.bottom,
diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java
index 9edf298b1fd2..24663862400d 100644
--- a/core/java/android/text/TextFlags.java
+++ b/core/java/android/text/TextFlags.java
@@ -55,10 +55,10 @@ public final class TextFlags {
* List of text flags to be transferred to the application process.
*/
public static final String[] TEXT_ACONFIGS_FLAGS = {
- Flags.FLAG_DEPRECATE_FONTS_XML,
Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN,
Flags.FLAG_PHRASE_STRICT_FALLBACK,
Flags.FLAG_USE_BOUNDS_FOR_WIDTH,
+ Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE,
};
/**
@@ -67,10 +67,10 @@ public final class TextFlags {
* The order must be the same to the TEXT_ACONFIG_FLAGS.
*/
public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = {
- Flags.deprecateFontsXml(),
Flags.noBreakNoHyphenationSpan(),
Flags.phraseStrictFallback(),
Flags.useBoundsForWidth(),
+ Flags.fixLineHeightForLocale(),
};
/**
diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig
deleted file mode 100644
index 52fe8834f234..000000000000
--- a/core/java/android/text/flags/custom_locale_fallback.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "custom_locale_fallback"
- namespace: "text"
- description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
- is_fixed_read_only: true
- bug: "278768958"
-}
diff --git a/core/java/android/text/flags/deprecate_fonts_xml.aconfig b/core/java/android/text/flags/deprecate_fonts_xml.aconfig
deleted file mode 100644
index 53621385dd4b..000000000000
--- a/core/java/android/text/flags/deprecate_fonts_xml.aconfig
+++ /dev/null
@@ -1,10 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "deprecate_fonts_xml"
- namespace: "text"
- description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
- # Make read only, as it could be used before the Settings provider is initialized.
- is_fixed_read_only: true
- bug: "281769620"
-}
diff --git a/core/java/android/text/flags/fix_double_underline.aconfig b/core/java/android/text/flags/fix_double_underline.aconfig
deleted file mode 100644
index b0aa72a765cc..000000000000
--- a/core/java/android/text/flags/fix_double_underline.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "fix_double_underline"
- namespace: "text"
- description: "Feature flag for fixing double underline because of the multiple font used in the single line."
- bug: "297336724"
-}
diff --git a/core/java/android/text/flags/fix_line_height_for_locale.aconfig b/core/java/android/text/flags/fix_line_height_for_locale.aconfig
deleted file mode 100644
index 8696bfa61e5c..000000000000
--- a/core/java/android/text/flags/fix_line_height_for_locale.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "fix_line_height_for_locale"
- namespace: "text"
- description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
- bug: "303326708"
-}
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
new file mode 100644
index 000000000000..201f680860f5
--- /dev/null
+++ b/core/java/android/text/flags/flags.aconfig
@@ -0,0 +1,63 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "vendor_custom_locale_fallback"
+ namespace: "text"
+ description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
+ is_fixed_read_only: true
+ bug: "278768958"
+}
+
+flag {
+ name: "new_fonts_fallback_xml"
+ namespace: "text"
+ description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml"
+ # Make read only, as it could be used before the Settings provider is initialized.
+ is_fixed_read_only: true
+ bug: "281769620"
+}
+
+flag {
+ name: "fix_double_underline"
+ namespace: "text"
+ description: "Feature flag for fixing double underline because of the multiple font used in the single line."
+ bug: "297336724"
+}
+
+flag {
+ name: "fix_line_height_for_locale"
+ namespace: "text"
+ description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin"
+ bug: "303326708"
+}
+
+flag {
+ name: "no_break_no_hyphenation_span"
+ namespace: "text"
+ description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+ bug: "283193586"
+}
+
+flag {
+ name: "use_optimized_boottime_font_loading"
+ namespace: "text"
+ description: "Feature flag ensuring that font is loaded once and asynchronously."
+ # Make read only, as font loading is in the critical boot path which happens before the read-write
+ # flags propagate to the device.
+ is_fixed_read_only: true
+ bug: "304406888"
+}
+
+flag {
+ name: "phrase_strict_fallback"
+ namespace: "text"
+ description: "Feature flag for automatic fallback from phrase based line break to strict line break."
+ bug: "281970875"
+}
+
+flag {
+ name: "use_bounds_for_width"
+ namespace: "text"
+ description: "Feature flag for preventing horizontal clipping."
+ bug: "63938206"
+}
diff --git a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
deleted file mode 100644
index 60f1e8802ea1..000000000000
--- a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "no_break_no_hyphenation_span"
- namespace: "text"
- description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
- bug: "283193586"
-}
diff --git a/core/java/android/text/flags/optimized_font_loading.aconfig b/core/java/android/text/flags/optimized_font_loading.aconfig
deleted file mode 100644
index 0e4a69f90375..000000000000
--- a/core/java/android/text/flags/optimized_font_loading.aconfig
+++ /dev/null
@@ -1,11 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "use_optimized_boottime_font_loading"
- namespace: "text"
- description: "Feature flag ensuring that font is loaded once and asynchronously."
- # Make read only, as font loading is in the critical boot path which happens before the read-write
- # flags propagate to the device.
- is_fixed_read_only: true
- bug: "304406888"
-}
diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig
deleted file mode 100644
index c67a21bca0b4..000000000000
--- a/core/java/android/text/flags/phrase_strict_fallback.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "phrase_strict_fallback"
- namespace: "text"
- description: "Feature flag for automatic fallback from phrase based line break to strict line break."
- bug: "281970875"
-}
diff --git a/core/java/android/text/flags/use_bounds_for_width.aconfig b/core/java/android/text/flags/use_bounds_for_width.aconfig
deleted file mode 100644
index d89d5f4c7216..000000000000
--- a/core/java/android/text/flags/use_bounds_for_width.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "com.android.text.flags"
-
-flag {
- name: "use_bounds_for_width"
- namespace: "text"
- description: "Feature flag for preventing horizontal clipping."
- bug: "63938206"
-}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 506945501609..07dd882807af 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -18,8 +18,10 @@ package android.view;
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
+import static android.hardware.flags.Flags.FLAG_OVERLAYPROPERTIES_CLASS_API;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1468,17 +1470,18 @@ public final class Display {
}
/**
- * Returns null if it's virtual display.
- * @hide
+ * Returns the {@link OverlayProperties} of the display.
*/
- @Nullable
+ @FlaggedApi(FLAG_OVERLAYPROPERTIES_CLASS_API)
+ @NonNull
public OverlayProperties getOverlaySupport() {
synchronized (mLock) {
updateDisplayInfoLocked();
- if (mDisplayInfo.type != TYPE_VIRTUAL) {
+ if (mDisplayInfo.type == TYPE_INTERNAL
+ || mDisplayInfo.type == TYPE_EXTERNAL) {
return mGlobal.getOverlaySupport();
}
- return null;
+ return OverlayProperties.getDefault();
}
}
@@ -2091,7 +2094,8 @@ public final class Display {
private final int mModeId;
private final int mWidth;
private final int mHeight;
- private final float mRefreshRate;
+ private final float mPeakRefreshRate;
+ private final float mVsyncRate;
@NonNull
private final float[] mAlternativeRefreshRates;
@NonNull
@@ -2103,7 +2107,15 @@ public final class Display {
*/
@TestApi
public Mode(int width, int height, float refreshRate) {
- this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]);
+ this(INVALID_MODE_ID, width, height, refreshRate, refreshRate, new float[0],
+ new int[0]);
+ }
+
+ /**
+ * @hide
+ */
+ public Mode(int width, int height, float refreshRate, float vsyncRate) {
+ this(INVALID_MODE_ID, width, height, refreshRate, vsyncRate, new float[0], new int[0]);
}
/**
@@ -2111,18 +2123,29 @@ public final class Display {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Mode(int modeId, int width, int height, float refreshRate) {
- this(modeId, width, height, refreshRate, new float[0], new int[0]);
+ this(modeId, width, height, refreshRate, refreshRate, new float[0], new int[0]);
}
/**
* @hide
*/
public Mode(int modeId, int width, int height, float refreshRate,
+ float[] alternativeRefreshRates,
+ @HdrCapabilities.HdrType int[] supportedHdrTypes) {
+ this(modeId, width, height, refreshRate, refreshRate, alternativeRefreshRates,
+ supportedHdrTypes);
+ }
+
+ /**
+ * @hide
+ */
+ public Mode(int modeId, int width, int height, float refreshRate, float vsyncRate,
float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) {
mModeId = modeId;
mWidth = width;
mHeight = height;
- mRefreshRate = refreshRate;
+ mPeakRefreshRate = refreshRate;
+ mVsyncRate = vsyncRate;
mAlternativeRefreshRates =
Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
Arrays.sort(mAlternativeRefreshRates);
@@ -2173,7 +2196,17 @@ public final class Display {
* Returns the refresh rate in frames per second.
*/
public float getRefreshRate() {
- return mRefreshRate;
+ return mPeakRefreshRate;
+ }
+
+ /**
+ * Returns the vsync rate in frames per second.
+ * The physical vsync rate may be higher than the refresh rate, as the refresh rate may be
+ * constrained by the system.
+ * @hide
+ */
+ public float getVsyncRate() {
+ return mVsyncRate;
}
/**
@@ -2219,7 +2252,7 @@ public final class Display {
public boolean matches(int width, int height, float refreshRate) {
return mWidth == width &&
mHeight == height &&
- Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate);
+ Float.floatToIntBits(mPeakRefreshRate) == Float.floatToIntBits(refreshRate);
}
/**
@@ -2232,9 +2265,9 @@ public final class Display {
*
* @hide
*/
- public boolean matchesIfValid(int width, int height, float refreshRate) {
+ public boolean matchesIfValid(int width, int height, float peakRefreshRate) {
if (!isWidthValid(width) && !isHeightValid(height)
- && !isRefreshRateValid(refreshRate)) {
+ && !isRefreshRateValid(peakRefreshRate)) {
return false;
}
if (isWidthValid(width) != isHeightValid(height)) {
@@ -2242,8 +2275,9 @@ public final class Display {
}
return (!isWidthValid(width) || mWidth == width)
&& (!isHeightValid(height) || mHeight == height)
- && (!isRefreshRateValid(refreshRate)
- || Float.floatToIntBits(mRefreshRate) == Float.floatToIntBits(refreshRate));
+ && (!isRefreshRateValid(peakRefreshRate)
+ || Float.floatToIntBits(mPeakRefreshRate)
+ == Float.floatToIntBits(peakRefreshRate));
}
/**
@@ -2262,7 +2296,7 @@ public final class Display {
* @hide
*/
public boolean isRefreshRateSet() {
- return mRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
+ return mPeakRefreshRate != INVALID_DISPLAY_REFRESH_RATE;
}
/**
@@ -2283,7 +2317,8 @@ public final class Display {
return false;
}
Mode that = (Mode) other;
- return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
+ return mModeId == that.mModeId
+ && matches(that.mWidth, that.mHeight, that.mPeakRefreshRate)
&& Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates)
&& Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes);
}
@@ -2294,7 +2329,8 @@ public final class Display {
hash = hash * 17 + mModeId;
hash = hash * 17 + mWidth;
hash = hash * 17 + mHeight;
- hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
+ hash = hash * 17 + Float.floatToIntBits(mPeakRefreshRate);
+ hash = hash * 17 + Float.floatToIntBits(mVsyncRate);
hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
return hash;
@@ -2306,7 +2342,8 @@ public final class Display {
.append("id=").append(mModeId)
.append(", width=").append(mWidth)
.append(", height=").append(mHeight)
- .append(", fps=").append(mRefreshRate)
+ .append(", fps=").append(mPeakRefreshRate)
+ .append(", vsync=").append(mVsyncRate)
.append(", alternativeRefreshRates=")
.append(Arrays.toString(mAlternativeRefreshRates))
.append(", supportedHdrTypes=")
@@ -2321,8 +2358,8 @@ public final class Display {
}
private Mode(Parcel in) {
- this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(),
- in.createIntArray());
+ this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.readFloat(),
+ in.createFloatArray(), in.createIntArray());
}
@Override
@@ -2330,7 +2367,8 @@ public final class Display {
out.writeInt(mModeId);
out.writeInt(mWidth);
out.writeInt(mHeight);
- out.writeFloat(mRefreshRate);
+ out.writeFloat(mPeakRefreshRate);
+ out.writeFloat(mVsyncRate);
out.writeFloatArray(mAlternativeRefreshRates);
out.writeIntArray(mSupportedHdrTypes);
}
@@ -2656,6 +2694,7 @@ public final class Display {
if (displayId == getDisplayId()) {
float newRatio = getHdrSdrRatio();
if (newRatio != mLastReportedRatio) {
+ mLastReportedRatio = newRatio;
mListener.accept(Display.this);
}
}
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
index 1310b0ccd3a9..6b354a0e232f 100644
--- a/core/java/android/view/HapticScrollFeedbackProvider.java
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -16,16 +16,10 @@
package android.view;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.view.flags.Flags;
import com.android.internal.annotations.VisibleForTesting;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
*
@@ -36,16 +30,12 @@ import java.lang.annotation.RetentionPolicy;
* methods in this class. To check if your input device ID, source, and motion axis are valid for
* haptic feedback, you can use the
* {@link ViewConfiguration#isHapticScrollFeedbackEnabled(int, int, int)} API.
+ *
+ * @hide
*/
-@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
private static final String TAG = "HapticScrollFeedbackProvider";
- /** @hide */
- @IntDef(value = {MotionEvent.AXIS_SCROLL})
- @Retention(RetentionPolicy.SOURCE)
- public @interface HapticScrollFeedbackAxis {}
-
private static final int TICK_INTERVAL_NO_TICK = 0;
private static final boolean INITIAL_END_OF_LIST_HAPTICS_ENABLED = false;
@@ -89,8 +79,7 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
}
@Override
- public void onScrollProgress(
- int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, int deltaInPixels) {
+ public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
@@ -117,8 +106,7 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
}
@Override
- public void onScrollLimit(
- int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, boolean isStart) {
+ public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
@@ -135,7 +123,7 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
}
@Override
- public void onSnapToItem(int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis) {
+ public void onSnapToItem(int inputDeviceId, int source, int axis) {
maybeUpdateCurrentConfig(inputDeviceId, source, axis);
if (!mHapticScrollFeedbackEnabled) {
return;
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index fb24211e591a..90663c7ad38e 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1283,7 +1283,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
- ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if ((types & mTypesBeingCancelled) != 0) {
final boolean monitoredAnimation =
animationType == ANIMATION_TYPE_SHOW || animationType == ANIMATION_TYPE_HIDE;
@@ -1295,12 +1294,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
ImeTracker.forLatency().onHideCancelled(statsToken,
PHASE_CLIENT_ANIMATION_CANCEL, ActivityThread::currentApplication);
}
+ ImeTracker.forLogging().onCancelled(statsToken,
+ ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
}
throw new IllegalStateException("Cannot start a new insets animation of "
+ Type.toString(types)
+ " while an existing " + Type.toString(mTypesBeingCancelled)
+ " is being cancelled.");
}
+ ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
@@ -1309,8 +1311,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
return;
}
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
mLastStartedAnimTypes |= types;
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 0ba414817247..8a44d4fa5934 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -17,6 +17,7 @@
package android.view;
import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
import android.view.flags.Flags;
/**
@@ -62,23 +63,37 @@ import android.view.flags.Flags;
* </ul>
*
* <b>Note</b> that not all valid input device source and motion axis inputs are necessarily
- * supported for scroll feedback. If you are implementing this interface, provide clear
- * documentation in your implementation class about which input device source and motion axis are
- * supported for your specific implementation. If you are using one of the implementations of this
- * interface, please refer to the documentation of the implementation for details on which input
- * device source and axis are supported.
+ * supported for scroll feedback; the implementation may choose to provide no feedback for some
+ * valid input device source and motion axis arguments.
*/
@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public interface ScrollFeedbackProvider {
+
/**
- * Call this when the view has snapped to an item.
+ * Creates a {@link ScrollFeedbackProvider} implementation for this device.
*
+ * <p>Use a feedback provider created by this method, unless you intend to use your custom
+ * scroll feedback providing logic. This allows your use cases to generate scroll feedback that
+ * is consistent with the rest of the use cases on the device.
+ *
+ * @param view the {@link View} for which to provide scroll feedback.
+ * @return the default {@link ScrollFeedbackProvider} implementation for the device.
+ */
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
+ @NonNull
+ static ScrollFeedbackProvider createProvider(@NonNull View view) {
+ return new HapticScrollFeedbackProvider(view);
+ }
+
+ /**
+ * Call this when the view has snapped to an item.
*
* @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
* the snap.
* @param source the input source of the motion causing the snap.
* @param axis the axis of {@code event} that caused the item to snap.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onSnapToItem(int inputDeviceId, int source, int axis);
/**
@@ -99,6 +114,7 @@ public interface ScrollFeedbackProvider {
* "start" for some views may be at the bottom of a scrolling list, while it may
* be at the top of scrolling list for others.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart);
/**
@@ -122,5 +138,6 @@ public interface ScrollFeedbackProvider {
* @param axis the axis of {@code event} that caused scroll progress.
* @param deltaInPixels the amount of scroll progress, in pixels.
*/
+ @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2f3d73a36425..e22207c9771d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -70,6 +70,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
@@ -796,7 +797,7 @@ public final class SurfaceControl implements Parcelable {
if (nativeObject != 0) {
// Only add valid surface controls to the registry. This is called at the end of this
// method since its information is dumped if the process threshold is reached.
- addToRegistry();
+ SurfaceControlRegistry.getProcessInstance().add(this);
}
}
@@ -1501,7 +1502,7 @@ public final class SurfaceControl implements Parcelable {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- removeFromRegistry();
+ SurfaceControlRegistry.getProcessInstance().remove(this);
} finally {
super.finalize();
}
@@ -1519,6 +1520,10 @@ public final class SurfaceControl implements Parcelable {
*/
public void release() {
if (mNativeObject != 0) {
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "release", null, this, null);
+ }
mFreeNativeResources.run();
mNativeObject = 0;
mNativeHandle = 0;
@@ -1532,7 +1537,7 @@ public final class SurfaceControl implements Parcelable {
mChoreographer = null;
}
}
- removeFromRegistry();
+ SurfaceControlRegistry.getProcessInstance().remove(this);
}
}
@@ -1791,7 +1796,7 @@ public final class SurfaceControl implements Parcelable {
public float yDpi;
// Some modes have peak refresh rate lower than the panel vsync rate.
- public float refreshRate;
+ public float peakRefreshRate;
// Fixed rate of vsync deadlines for the panel.
// This can be higher then the peak refresh rate for some panel technologies
// See: VrrConfig.aidl
@@ -1815,7 +1820,7 @@ public final class SurfaceControl implements Parcelable {
+ ", height=" + height
+ ", xDpi=" + xDpi
+ ", yDpi=" + yDpi
- + ", refreshRate=" + refreshRate
+ + ", peakRefreshRate=" + peakRefreshRate
+ ", vsyncRate=" + vsyncRate
+ ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
+ ", presentationDeadlineNanos=" + presentationDeadlineNanos
@@ -1833,7 +1838,7 @@ public final class SurfaceControl implements Parcelable {
&& height == that.height
&& Float.compare(that.xDpi, xDpi) == 0
&& Float.compare(that.yDpi, yDpi) == 0
- && Float.compare(that.refreshRate, refreshRate) == 0
+ && Float.compare(that.peakRefreshRate, peakRefreshRate) == 0
&& Float.compare(that.vsyncRate, vsyncRate) == 0
&& appVsyncOffsetNanos == that.appVsyncOffsetNanos
&& presentationDeadlineNanos == that.presentationDeadlineNanos
@@ -1843,7 +1848,7 @@ public final class SurfaceControl implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, vsyncRate,
+ return Objects.hash(id, width, height, xDpi, yDpi, peakRefreshRate, vsyncRate,
appVsyncOffsetNanos, presentationDeadlineNanos, group,
Arrays.hashCode(supportedHdrTypes));
}
@@ -2765,8 +2770,10 @@ public final class SurfaceControl implements Parcelable {
private Transaction(long nativeObject) {
mNativeObject = nativeObject;
- mFreeNativeResources =
- sRegistry.registerNativeAllocation(this, mNativeObject);
+ mFreeNativeResources = sRegistry.registerNativeAllocation(this, mNativeObject);
+ if (!SurfaceControlRegistry.sCallStackDebuggingInitialized) {
+ SurfaceControlRegistry.initializeCallStackDebugging();
+ }
}
private Transaction(Parcel in) {
@@ -2845,6 +2852,11 @@ public final class SurfaceControl implements Parcelable {
applyResizedSurfaces();
notifyReparentedSurfaces();
nativeApplyTransaction(mNativeObject, sync, oneWay);
+
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "apply", this, null, null);
+ }
}
/**
@@ -2920,6 +2932,10 @@ public final class SurfaceControl implements Parcelable {
@UnsupportedAppUsage
public Transaction show(SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "show", this, sc, null);
+ }
nativeSetFlags(mNativeObject, sc.mNativeObject, 0, SURFACE_HIDDEN);
return this;
}
@@ -2934,6 +2950,10 @@ public final class SurfaceControl implements Parcelable {
@UnsupportedAppUsage
public Transaction hide(SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "hide", this, sc, null);
+ }
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_HIDDEN, SURFACE_HIDDEN);
return this;
}
@@ -2950,6 +2970,10 @@ public final class SurfaceControl implements Parcelable {
@NonNull
public Transaction setPosition(@NonNull SurfaceControl sc, float x, float y) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setPosition", this, sc, "x=" + x + " y=" + y);
+ }
nativeSetPosition(mNativeObject, sc.mNativeObject, x, y);
return this;
}
@@ -2968,6 +2992,10 @@ public final class SurfaceControl implements Parcelable {
checkPreconditions(sc);
Preconditions.checkArgument(scaleX >= 0, "Negative value passed in for scaleX");
Preconditions.checkArgument(scaleY >= 0, "Negative value passed in for scaleY");
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setScale", this, sc, "sx=" + scaleX + " sy=" + scaleY);
+ }
nativeSetScale(mNativeObject, sc.mNativeObject, scaleX, scaleY);
return this;
}
@@ -2985,6 +3013,10 @@ public final class SurfaceControl implements Parcelable {
public Transaction setBufferSize(@NonNull SurfaceControl sc,
@IntRange(from = 0) int w, @IntRange(from = 0) int h) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setBufferSize", this, sc, "w=" + w + " h=" + h);
+ }
mResizedSurfaces.put(sc, new Point(w, h));
return this;
}
@@ -3005,6 +3037,10 @@ public final class SurfaceControl implements Parcelable {
public Transaction setFixedTransformHint(@NonNull SurfaceControl sc,
@Surface.Rotation int transformHint) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setFixedTransformHint", this, sc, "hint=" + transformHint);
+ }
nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, transformHint);
return this;
}
@@ -3018,6 +3054,10 @@ public final class SurfaceControl implements Parcelable {
@NonNull
public Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "unsetFixedTransformHint", this, sc, null);
+ }
nativeSetFixedTransformHint(mNativeObject, sc.mNativeObject, -1/* INVALID_ROTATION */);
return this;
}
@@ -3035,6 +3075,10 @@ public final class SurfaceControl implements Parcelable {
public Transaction setLayer(@NonNull SurfaceControl sc,
@IntRange(from = Integer.MIN_VALUE, to = Integer.MAX_VALUE) int z) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setLayer", this, sc, "z=" + z);
+ }
nativeSetLayer(mNativeObject, sc.mNativeObject, z);
return this;
}
@@ -3044,6 +3088,10 @@ public final class SurfaceControl implements Parcelable {
*/
public Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo, int z) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setRelativeLayer", this, sc, "relTo=" + relativeTo + " z=" + z);
+ }
nativeSetRelativeLayer(mNativeObject, sc.mNativeObject, relativeTo.mNativeObject, z);
return this;
}
@@ -3053,6 +3101,10 @@ public final class SurfaceControl implements Parcelable {
*/
public Transaction setTransparentRegionHint(SurfaceControl sc, Region transparentRegion) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "unsetFixedTransformHint", this, sc, "region=" + transparentRegion);
+ }
nativeSetTransparentRegionHint(mNativeObject,
sc.mNativeObject, transparentRegion);
return this;
@@ -3069,6 +3121,10 @@ public final class SurfaceControl implements Parcelable {
public Transaction setAlpha(@NonNull SurfaceControl sc,
@FloatRange(from = 0.0, to = 1.0) float alpha) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setAlpha", this, sc, "alpha=" + alpha);
+ }
nativeSetAlpha(mNativeObject, sc.mNativeObject, alpha);
return this;
}
@@ -3124,6 +3180,11 @@ public final class SurfaceControl implements Parcelable {
public Transaction setMatrix(SurfaceControl sc,
float dsdx, float dtdx, float dtdy, float dsdy) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setMatrix", this, sc,
+ "dsdx=" + dsdx + " dtdx=" + dtdx + " dtdy=" + dtdy + " dsdy=" + dsdy);
+ }
nativeSetMatrix(mNativeObject, sc.mNativeObject,
dsdx, dtdx, dtdy, dsdy);
return this;
@@ -3189,6 +3250,10 @@ public final class SurfaceControl implements Parcelable {
@UnsupportedAppUsage
public Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setWindowCrop", this, sc, "crop=" + crop);
+ }
if (crop != null) {
nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
crop.left, crop.top, crop.right, crop.bottom);
@@ -3211,6 +3276,10 @@ public final class SurfaceControl implements Parcelable {
*/
public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCrop", this, sc, "crop=" + crop);
+ }
if (crop != null) {
Preconditions.checkArgument(crop.isValid(), "Crop isn't valid.");
nativeSetWindowCrop(mNativeObject, sc.mNativeObject,
@@ -3233,6 +3302,10 @@ public final class SurfaceControl implements Parcelable {
*/
public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCornerRadius", this, sc, "w=" + width + " h=" + height);
+ }
nativeSetWindowCrop(mNativeObject, sc.mNativeObject, 0, 0, width, height);
return this;
}
@@ -3247,6 +3320,10 @@ public final class SurfaceControl implements Parcelable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setCornerRadius", this, sc, "cornerRadius=" + cornerRadius);
+ }
nativeSetCornerRadius(mNativeObject, sc.mNativeObject, cornerRadius);
return this;
@@ -3262,6 +3339,10 @@ public final class SurfaceControl implements Parcelable {
*/
public Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setBackgroundBlurRadius", this, sc, "radius=" + radius);
+ }
nativeSetBackgroundBlurRadius(mNativeObject, sc.mNativeObject, radius);
return this;
}
@@ -3318,6 +3399,10 @@ public final class SurfaceControl implements Parcelable {
public Transaction reparent(@NonNull SurfaceControl sc,
@Nullable SurfaceControl newParent) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "reparent", this, sc, "newParent=" + newParent);
+ }
long otherObject = 0;
if (newParent != null) {
newParent.checkNotReleased();
@@ -3337,6 +3422,11 @@ public final class SurfaceControl implements Parcelable {
@UnsupportedAppUsage
public Transaction setColor(SurfaceControl sc, @Size(3) float[] color) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "reparent", this, sc,
+ "r=" + color[0] + " g=" + color[1] + " b=" + color[2]);
+ }
nativeSetColor(mNativeObject, sc.mNativeObject, color);
return this;
}
@@ -3347,6 +3437,10 @@ public final class SurfaceControl implements Parcelable {
*/
public Transaction unsetColor(SurfaceControl sc) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "unsetColor", this, sc, null);
+ }
nativeSetColor(mNativeObject, sc.mNativeObject, INVALID_COLOR);
return this;
}
@@ -3358,6 +3452,10 @@ public final class SurfaceControl implements Parcelable {
*/
public Transaction setSecure(SurfaceControl sc, boolean isSecure) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setSecure", this, sc, "secure=" + isSecure);
+ }
if (isSecure) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SECURE, SECURE);
} else {
@@ -3411,6 +3509,10 @@ public final class SurfaceControl implements Parcelable {
@NonNull
public Transaction setOpaque(@NonNull SurfaceControl sc, boolean isOpaque) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setOpaque", this, sc, "opaque=" + isOpaque);
+ }
if (isOpaque) {
nativeSetFlags(mNativeObject, sc.mNativeObject, SURFACE_OPAQUE, SURFACE_OPAQUE);
} else {
@@ -3580,6 +3682,10 @@ public final class SurfaceControl implements Parcelable {
*/
public Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setShadowRadius", this, sc, "radius=" + shadowRadius);
+ }
nativeSetShadowRadius(mNativeObject, sc.mNativeObject, shadowRadius);
return this;
}
@@ -4463,26 +4569,6 @@ public final class SurfaceControl implements Parcelable {
return -1;
}
- /**
- * Adds this surface control to the registry for this process if it is created.
- */
- private void addToRegistry() {
- final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
- if (registry != null) {
- registry.add(this);
- }
- }
-
- /**
- * Removes this surface control from the registry for this process.
- */
- private void removeFromRegistry() {
- final SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
- if (registry != null) {
- registry.remove(this);
- }
- }
-
// Called by native
private static void invokeReleaseCallback(Consumer<SyncFence> callback, long nativeFencePtr) {
SyncFence fence = new SyncFence(nativeFencePtr);
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 0f35048cac67..52be8f6a76fd 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -23,7 +23,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.os.Build;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -104,6 +106,9 @@ public class SurfaceControlRegistry {
// Number of surface controls to dump when the max threshold is exceeded
private static final int DUMP_LIMIT = 256;
+ // An instance of a registry that is a no-op
+ private static final SurfaceControlRegistry NO_OP_REGISTRY = new NoOpRegistry();
+
// Static lock, must be held for all registry operations
private static final Object sLock = new Object();
@@ -113,6 +118,22 @@ public class SurfaceControlRegistry {
// The registry for a given process
private static volatile SurfaceControlRegistry sProcessRegistry;
+ // Whether call stack debugging has been initialized. This is evaluated only once per process
+ // instance when the first SurfaceControl.Transaction object is created
+ static boolean sCallStackDebuggingInitialized;
+
+ // Whether call stack debugging is currently enabled, ie. whether there is a valid match string
+ // for either a specific surface control name or surface control transaction method
+ static boolean sCallStackDebuggingEnabled;
+
+ // The name of the surface control to log stack traces for. Always non-null if
+ // sCallStackDebuggingEnabled is true. Can be combined with the match call.
+ private static String sCallStackDebuggingMatchName;
+
+ // The surface control transaction method name to log stack traces for. Always non-null if
+ // sCallStackDebuggingEnabled is true. Can be combined with the match name.
+ private static String sCallStackDebuggingMatchCall;
+
// Mapping of the active SurfaceControls to the elapsed time when they were registered
@GuardedBy("sLock")
private final WeakHashMap<SurfaceControl, Long> mSurfaceControls;
@@ -160,6 +181,12 @@ public class SurfaceControlRegistry {
}
}
+ @VisibleForTesting
+ public void setCallStackDebuggingParams(String matchName, String matchCall) {
+ sCallStackDebuggingMatchName = matchName.toLowerCase();
+ sCallStackDebuggingMatchCall = matchCall.toLowerCase();
+ }
+
/**
* Creates and initializes the registry for all SurfaceControls in this process. The caller must
* hold the READ_FRAME_BUFFER permission.
@@ -196,11 +223,9 @@ public class SurfaceControlRegistry {
* createProcessInstance(Context) was previously called from a valid caller.
* @hide
*/
- @Nullable
- @VisibleForTesting
public static SurfaceControlRegistry getProcessInstance() {
synchronized (sLock) {
- return sProcessRegistry;
+ return sProcessRegistry != null ? sProcessRegistry : NO_OP_REGISTRY;
}
}
@@ -248,6 +273,91 @@ public class SurfaceControlRegistry {
}
/**
+ * Initializes global call stack debugging if this is a debug build and a filter is specified.
+ * This is a no-op if
+ *
+ * Usage:
+ * adb shell setprop persist.wm.debug.sc.tx.log_match_call <call or \"\" to unset>
+ * adb shell setprop persist.wm.debug.sc.tx.log_match_name <name or \"\" to unset>
+ * adb reboot
+ */
+ final static void initializeCallStackDebugging() {
+ if (sCallStackDebuggingInitialized || !Build.IS_DEBUGGABLE) {
+ // Return early if already initialized or this is not a debug build
+ return;
+ }
+
+ sCallStackDebuggingInitialized = true;
+ sCallStackDebuggingMatchCall =
+ SystemProperties.get("persist.wm.debug.sc.tx.log_match_call", null)
+ .toLowerCase();
+ sCallStackDebuggingMatchName =
+ SystemProperties.get("persist.wm.debug.sc.tx.log_match_name", null)
+ .toLowerCase();
+ // Only enable stack debugging if any of the match filters are set
+ sCallStackDebuggingEnabled = (!sCallStackDebuggingMatchCall.isEmpty()
+ || !sCallStackDebuggingMatchName.isEmpty());
+ if (sCallStackDebuggingEnabled) {
+ Log.d(TAG, "Enabling transaction call stack debugging:"
+ + " matchCall=" + sCallStackDebuggingMatchCall
+ + " matchName=" + sCallStackDebuggingMatchName);
+ }
+ }
+
+ /**
+ * Dumps the callstack if it matches the global debug properties. Caller should first verify
+ * {@link #sCallStackDebuggingEnabled} is true.
+ *
+ * @param call the name of the call
+ * @param tx (optional) the transaction associated with this call
+ * @param sc the affected surface
+ * @param details additional details to print with the stack track
+ */
+ final void checkCallStackDebugging(@NonNull String call,
+ @Nullable SurfaceControl.Transaction tx, @Nullable SurfaceControl sc,
+ @Nullable String details) {
+ if (!sCallStackDebuggingEnabled) {
+ return;
+ }
+ if (!matchesForCallStackDebugging(sc != null ? sc.getName() : null, call)) {
+ return;
+ }
+ final String txMsg = tx != null ? "tx=" + tx.getId() + " ": "";
+ final String scMsg = sc != null ? " sc=" + sc.getName() + "": "";
+ final String msg = details != null
+ ? call + " (" + txMsg + scMsg + ") " + details
+ : call + " (" + txMsg + scMsg + ")";
+ Log.e(TAG, msg, new Throwable());
+ }
+
+ /**
+ * Tests whether the given surface control name/method call matches the filters set for the
+ * call stack debugging.
+ */
+ @VisibleForTesting
+ public final boolean matchesForCallStackDebugging(@Nullable String name, @NonNull String call) {
+ final boolean matchCall = !sCallStackDebuggingMatchCall.isEmpty();
+ if (matchCall && !call.toLowerCase().contains(sCallStackDebuggingMatchCall)) {
+ // Skip if target call doesn't match requested caller
+ return false;
+ }
+ final boolean matchName = !sCallStackDebuggingMatchName.isEmpty();
+ if (matchName && (name == null
+ || !name.toLowerCase().contains(sCallStackDebuggingMatchName))) {
+ // Skip if target surface doesn't match requested surface
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns whether call stack debugging is enabled for this process.
+ */
+ final static boolean isCallStackDebuggingEnabled() {
+ return sCallStackDebuggingEnabled;
+ }
+
+ /**
* Forces the gc and finalizers to run, used prior to dumping to ensure we only dump strongly
* referenced surface controls.
*/
@@ -272,7 +382,27 @@ public class SurfaceControlRegistry {
synchronized (sLock) {
if (sProcessRegistry != null) {
sDefaultReporter.onMaxLayersExceeded(sProcessRegistry.mSurfaceControls, limit, pw);
+ pw.println("sCallStackDebuggingInitialized=" + sCallStackDebuggingInitialized);
+ pw.println("sCallStackDebuggingEnabled=" + sCallStackDebuggingEnabled);
+ pw.println("sCallStackDebuggingMatchName=" + sCallStackDebuggingMatchName);
+ pw.println("sCallStackDebuggingMatchCall=" + sCallStackDebuggingMatchCall);
}
}
}
+
+ /**
+ * A no-op implementation of the registry.
+ */
+ private static class NoOpRegistry extends SurfaceControlRegistry {
+
+ @Override
+ public void setReportingThresholds(int maxLayersReportingThreshold,
+ int resetReportingThreshold, Reporter reporter) {}
+
+ @Override
+ void add(SurfaceControl sc) {}
+
+ @Override
+ void remove(SurfaceControl sc) {}
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 49b16c7697c9..e9d0e4c557c6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19,8 +19,6 @@ package android.view;
import static android.content.res.Resources.ID_NULL;
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
-import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -29,7 +27,6 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH;
import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
-import static android.view.flags.Flags.toolkitSetFrameRate;
import static android.view.flags.Flags.viewVelocityApi;
import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
@@ -117,7 +114,6 @@ import android.sysprop.DisplayProperties;
import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
import android.util.FloatProperty;
import android.util.LayoutDirection;
import android.util.Log;
@@ -5513,11 +5509,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private ViewTranslationResponse mViewTranslationResponse;
/**
- * A threshold value to determine the frame rate category of the View based on the size.
- */
- private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
-
- /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -20192,9 +20183,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return;
}
- // For VRR to vote the preferred frame rate
- votePreferredFrameRate();
-
// Reset content capture caches
mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
mContentCaptureSessionCached = false;
@@ -20297,8 +20285,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
protected void damageInParent() {
if (mParent != null && mAttachInfo != null) {
- // For VRR to vote the preferred frame rate
- votePreferredFrameRate();
mParent.onDescendantInvalidated(this, this);
}
}
@@ -32995,40 +32981,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return null;
}
- private float getSizePercentage() {
- if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) {
- return 0;
- }
-
- DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
- int screenSize = displayMetrics.widthPixels
- * displayMetrics.heightPixels;
- int viewSize = getWidth() * getHeight();
-
- if (screenSize == 0 || viewSize == 0) {
- return 0f;
- }
- return (float) viewSize / screenSize;
- }
-
- private int calculateFrameRateCategory() {
- float sizePercentage = getSizePercentage();
-
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_LOW;
- } else {
- return FRAME_RATE_CATEGORY_NORMAL;
- }
- }
-
- private void votePreferredFrameRate() {
- // use toolkitSetFrameRate flag to gate the change
- ViewRootImpl viewRootImpl = getViewRootImpl();
- if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) {
- viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory());
- }
- }
-
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 2cf5d5d63596..ec961674db60 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,7 +16,6 @@
package android.view;
-import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
@@ -1272,12 +1271,10 @@ public class ViewConfiguration {
* @see InputDevice#getMotionRanges()
* @see InputDevice#getMotionRange(int)
* @see InputDevice#getMotionRange(int, int)
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
- public boolean isHapticScrollFeedbackEnabled(
- int inputDeviceId,
- @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
- int source) {
+ public boolean isHapticScrollFeedbackEnabled(int inputDeviceId, int axis, int source) {
if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return false;
if (source == InputDevice.SOURCE_ROTARY_ENCODER && axis == MotionEvent.AXIS_SCROLL) {
@@ -1318,12 +1315,10 @@ public class ViewConfiguration {
* returns {@code Integer.MAX_VALUE}.
*
* @see #isHapticScrollFeedbackEnabled(int, int, int)
+ *
+ * @hide
*/
- @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
- public int getHapticScrollFeedbackTickInterval(
- int inputDeviceId,
- @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
- int source) {
+ public int getHapticScrollFeedbackTickInterval(int inputDeviceId, int axis, int source) {
if (!mRotaryEncoderHapticScrollFeedbackEnabled) {
return NO_HAPTIC_SCROLL_TICK_INTERVAL;
}
@@ -1343,9 +1338,6 @@ public class ViewConfiguration {
* Checks if the View-based haptic scroll feedback implementation is enabled for
* {@link InputDevice#SOURCE_ROTARY_ENCODER}s.
*
- * <p>If this method returns {@code true}, the {@link HapticScrollFeedbackProvider} will be
- * muted for rotary encoders in favor of View's scroll haptics implementation.
- *
* @hide
*/
public boolean isViewBasedRotaryEncoderHapticScrollFeedbackEnabled() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 52b7cb18259f..dfada58771a6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -24,8 +24,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -76,10 +74,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_E
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -92,7 +87,6 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
-import static android.view.flags.Flags.toolkitSetFrameRate;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@@ -661,6 +655,10 @@ public final class ViewRootImpl implements ViewParent,
*/
private boolean mCheckIfCanDraw = false;
+ private boolean mWasLastDrawCanceled;
+ private boolean mLastTraversalWasVisible = true;
+ private boolean mLastDrawScreenOff;
+
private boolean mDrewOnceForSync = false;
int mSyncSeqId = 0;
@@ -738,7 +736,6 @@ public final class ViewRootImpl implements ViewParent,
private SurfaceControl mBoundsLayer;
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final Transaction mTransaction = new Transaction();
- private final Transaction mFrameRateTransaction = new Transaction();
@UnsupportedAppUsage
boolean mAdded;
@@ -962,34 +959,6 @@ public final class ViewRootImpl implements ViewParent,
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
- /*
- * for Variable Refresh Rate project
- */
-
- // The preferred frame rate category of the view that
- // could be updated on a frame-by-frame basis.
- private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- // The preferred frame rate category of the last frame that
- // could be used to lower frame rate after touch boost
- private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- // The preferred frame rate of the view that is mainly used for
- // touch boosting, view velocity handling, and TextureView.
- private float mPreferredFrameRate = 0;
- // Used to check if there were any view invalidations in
- // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
- private boolean mHasInvalidation = false;
- // Used to check if it is in the touch boosting period.
- private boolean mIsFrameRateBoosting = false;
- // Used to check if there is a message in the message queue
- // for idleness handling.
- private boolean mHasIdledMessage = false;
- // time for touch boost period.
- private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500;
- // time for checking idle status periodically.
- private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
- // time for revaluating the idle status before lowering the frame rate.
- private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
-
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1051,7 +1020,8 @@ public final class ViewRootImpl implements ViewParent,
mDisplay = display;
mBasePackageName = context.getBasePackageName();
final String name = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayListenerLogging = !TextUtils.isEmpty(name) && name.equals(mBasePackageName);
+ // TODO: b/306170135 - return to using textutils check on package name.
+ mExtraDisplayListenerLogging = true;
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
@@ -1925,12 +1895,19 @@ public final class ViewRootImpl implements ViewParent,
}
void handleAppVisibility(boolean visible) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW, TextUtils.formatSimple(
+ "%s visibilityChanged oldVisibility=%b newVisibility=%b", mTag,
+ mAppVisible, visible));
+ }
if (mAppVisible != visible) {
final boolean previousVisible = getHostVisibility() == View.VISIBLE;
mAppVisible = visible;
final boolean currentVisible = getHostVisibility() == View.VISIBLE;
// Root view only cares about whether it is visible or not.
if (previousVisible != currentVisible) {
+ Log.d(mTag, "visibilityChanged oldVisibility=" + previousVisible + " newVisibility="
+ + currentVisible);
mAppVisibilityChanged = true;
scheduleTraversals();
}
@@ -2038,6 +2015,10 @@ public final class ViewRootImpl implements ViewParent,
Slog.i(mTag, "DisplayState - old: " + oldDisplayState
+ ", new: " + newDisplayState);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceCounter(Trace.TRACE_TAG_WINDOW_MANAGER,
+ "vri#screenState[" + mTag + "] state=", newDisplayState);
+ }
if (oldDisplayState != newDisplayState) {
mAttachInfo.mDisplayState = newDisplayState;
pokeDrawLockIfNeeded();
@@ -3287,8 +3268,8 @@ public final class ViewRootImpl implements ViewParent,
|| mForceNextWindowRelayout) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- TextUtils.formatSimple("relayoutWindow#"
- + "first=%b/resize=%b/vis=%b/params=%b/force=%b",
+ TextUtils.formatSimple("%s-relayoutWindow#"
+ + "first=%b/resize=%b/vis=%b/params=%b/force=%b", mTag,
mFirst, windowShouldResize, viewVisibilityChanged, params != null,
mForceNextWindowRelayout));
}
@@ -3877,11 +3858,7 @@ public final class ViewRootImpl implements ViewParent,
boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
boolean cancelAndRedraw = cancelDueToPreDrawListener
|| (cancelDraw && mDrewOnceForSync);
- if (cancelAndRedraw) {
- Log.d(mTag, "Cancelling draw."
- + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
- + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
- }
+
if (!cancelAndRedraw) {
// A sync was already requested before the WMS requested sync. This means we need to
// sync the buffer, regardless if WMS wants to sync the buffer.
@@ -3905,6 +3882,9 @@ public final class ViewRootImpl implements ViewParent,
}
if (!isViewVisible) {
+ if (mLastTraversalWasVisible) {
+ logAndTrace("Not drawing due to not visible");
+ }
mLastPerformTraversalsSkipDrawReason = "view_not_visible";
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
@@ -3916,12 +3896,23 @@ public final class ViewRootImpl implements ViewParent,
handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
mPendingTransaction, "view not visible");
} else if (cancelAndRedraw) {
+ if (!mWasLastDrawCanceled) {
+ logAndTrace("Canceling draw."
+ + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+ + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
+ }
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
: "cancel_" + cancelReason;
// Try again
scheduleTraversals();
} else {
+ if (mWasLastDrawCanceled) {
+ logAndTrace("Draw frame after cancel");
+ }
+ if (!mLastTraversalWasVisible) {
+ logAndTrace("Start draw after previous draw not visible");
+ }
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
@@ -3933,6 +3924,8 @@ public final class ViewRootImpl implements ViewParent,
mPendingTransaction, mLastPerformDrawSkippedReason);
}
}
+ mWasLastDrawCanceled = cancelAndRedraw;
+ mLastTraversalWasVisible = isViewVisible;
if (mAttachInfo.mContentCaptureEvents != null) {
notifyContentCaptureEvents();
@@ -3953,12 +3946,6 @@ public final class ViewRootImpl implements ViewParent,
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}
-
- // For the variable refresh rate project.
- setPreferredFrameRate(mPreferredFrameRate);
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
- mLastPreferredFrameRateCategory = mPreferredFrameRateCategory;
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
}
private void createSyncIfNeeded() {
@@ -4728,10 +4715,7 @@ public final class ViewRootImpl implements ViewParent,
return didProduceBuffer -> {
if (!didProduceBuffer) {
- Trace.instant(Trace.TRACE_TAG_VIEW,
- "Transaction not synced due to no frame drawn-" + mTag);
- Log.d(mTag, "Pending transaction will not be applied in sync with a draw "
- + "because there was nothing new to draw");
+ logAndTrace("Transaction not synced due to no frame drawn");
mBlastBufferQueue.applyPendingTransactions(frame);
}
};
@@ -4748,17 +4732,26 @@ public final class ViewRootImpl implements ViewParent,
mLastPerformDrawSkippedReason = null;
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
mLastPerformDrawSkippedReason = "screen_off";
+ if (!mLastDrawScreenOff) {
+ logAndTrace("Not drawing due to screen off");
+ }
+ mLastDrawScreenOff = true;
return false;
} else if (mView == null) {
mLastPerformDrawSkippedReason = "no_root_view";
return false;
}
+ if (mLastDrawScreenOff) {
+ logAndTrace("Resumed drawing after screen turned on");
+ mLastDrawScreenOff = false;
+ }
+
final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw-" + mTag);
addFrameCommitCallbackIfNeeded();
@@ -6011,8 +6004,6 @@ public final class ViewRootImpl implements ViewParent,
private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;
private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
- private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
- private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
final class ViewRootHandler extends Handler {
@Override
@@ -6308,32 +6299,6 @@ public final class ViewRootImpl implements ViewParent,
mNumPausedForSync = 0;
scheduleTraversals();
break;
- case MSG_TOUCH_BOOST_TIMEOUT:
- /**
- * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
- */
- mIsFrameRateBoosting = false;
- setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
- mLastPreferredFrameRateCategory));
- break;
- case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting) {
- mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
- mHasIdledMessage = false;
- } else {
- /**
- * If there is no invalidation within a certain period,
- * we consider the display is idled.
- * We then set the frame rate catetogry to NO_PREFERENCE.
- * Note that SurfaceFlinger also has a mechanism to lower the refresh rate
- * if there is no updates of the buffer.
- */
- mHasInvalidation = false;
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
- FRAME_RATE_IDLENESS_REEVALUATE_TIME);
- }
- break;
}
}
}
@@ -7276,7 +7241,6 @@ public final class ViewRootImpl implements ViewParent,
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
- final int action = event.getAction();
boolean handled = mHandwritingInitiator.onTouchEvent(event);
if (handled) {
// If handwriting is started, toolkit doesn't receive ACTION_UP.
@@ -7297,22 +7261,6 @@ public final class ViewRootImpl implements ViewParent,
scheduleConsumeBatchedInputImmediately();
}
}
-
- // For the variable refresh rate project
- if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
- // set the frame rate to the maximum value.
- mIsFrameRateBoosting = true;
- setPreferredFrameRateCategory(mPreferredFrameRateCategory);
- }
- /**
- * We want to lower the refresh rate when MotionEvent.ACTION_UP,
- * MotionEvent.ACTION_CANCEL is detected.
- * Not using ACTION_MOVE to avoid checking and sending messages too frequently.
- */
- if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_CANCEL)) {
- sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME);
- }
return handled ? FINISH_HANDLED : FORWARD;
}
@@ -11512,8 +11460,7 @@ public final class ViewRootImpl implements ViewParent,
@Override
public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) {
if (mRemoved || !isHardwareEnabled()) {
- Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw applyImmediately-" + mTag);
- Log.d(mTag, "applyTransactionOnDraw: Applying transaction immediately");
+ logAndTrace("applyTransactionOnDraw applyImmediately");
t.apply();
} else {
Trace.instant(Trace.TRACE_TAG_VIEW, "applyTransactionOnDraw-" + mTag);
@@ -11901,93 +11848,10 @@ public final class ViewRootImpl implements ViewParent,
t.clearTrustedPresentationCallback(getSurfaceControl());
}
- private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
- if (!shouldSetFrameRateCategory()) {
- return;
- }
-
- int frameRateCategory = mIsFrameRateBoosting
- ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
-
- try {
- mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
- frameRateCategory, false).apply();
- } catch (Exception e) {
- Log.e(mTag, "Unable to set frame rate category", e);
- }
-
- if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) {
- // Check where the display is idled periodically.
- // If so, set the frame rate category to NO_PREFERENCE
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
- FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS);
- mHasIdledMessage = true;
- }
- }
-
- private void setPreferredFrameRate(float preferredFrameRate) {
- if (!shouldSetFrameRate()) {
- return;
- }
-
- try {
- mFrameRateTransaction.setFrameRate(mSurfaceControl,
- preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply();
- } catch (Exception e) {
- Log.e(mTag, "Unable to set frame rate", e);
+ private void logAndTrace(String msg) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
}
- }
-
- private void sendDelayedEmptyMessage(int message, int delayedTime) {
- mHandler.removeMessages(message);
-
- mHandler.sendEmptyMessageDelayed(message, delayedTime);
- }
-
- private boolean shouldSetFrameRateCategory() {
- // use toolkitSetFrameRate flag to gate the change
- return mSurface.isValid() && toolkitSetFrameRate();
- }
-
- private boolean shouldSetFrameRate() {
- // use toolkitSetFrameRate flag to gate the change
- return mPreferredFrameRate > 0 && toolkitSetFrameRate();
- }
-
- private boolean shouldTouchBoost(int motionEventAction, int windowType) {
- boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
- || motionEventAction == MotionEvent.ACTION_MOVE
- || motionEventAction == MotionEvent.ACTION_UP;
- boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION
- || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION;
- // use toolkitSetFrameRate flag to gate the change
- return desiredAction && desiredType && toolkitSetFrameRate();
- }
-
- /**
- * Allow Views to vote for the preferred frame rate category
- *
- * @param frameRateCategory the preferred frame rate category of a View
- */
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
- public void votePreferredFrameRateCategory(int frameRateCategory) {
- mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory);
- mHasInvalidation = true;
- }
-
- /**
- * Get the value of mPreferredFrameRateCategory
- */
- @VisibleForTesting
- public int getPreferredFrameRateCategory() {
- return mPreferredFrameRateCategory;
- }
-
- /**
- * Get the value of mPreferredFrameRate
- */
- @VisibleForTesting
- public float getPreferredFrameRate() {
- return mPreferredFrameRate;
+ Log.d(mTag, msg);
}
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index cc612ed93b2f..6888b50bb744 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -1,10 +1,12 @@
package: "android.view.accessibility"
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
flag {
+ name: "a11y_overlay_callbacks"
namespace: "accessibility"
- name: "force_invert_color"
- description: "Enable force force-dark for smart inversion and dark theme everywhere"
- bug: "282821643"
+ description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
+ bug: "304478691"
}
flag {
@@ -15,8 +17,8 @@ flag {
}
flag {
- name: "a11y_overlay_callbacks"
namespace: "accessibility"
- description: "Whether to allow the passing of result callbacks when attaching a11y overlays."
- bug: "304478691"
+ name: "force_invert_color"
+ description: "Enable force force-dark for smart inversion and dark theme everywhere"
+ bug: "282821643"
}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index 32256b9b09c8..b3359b711989 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -16,8 +16,8 @@
package android.view.animation;
-import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
-import static android.view.flags.Flags.expectedPresentationTimeApi;
+import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY;
+import static android.view.flags.Flags.expectedPresentationTimeReadOnly;
import android.annotation.AnimRes;
import android.annotation.FlaggedApi;
@@ -67,6 +67,11 @@ public class AnimationUtils {
@Overridable
public static final long OVERRIDE_ENABLE_EXPECTED_PRSENTATION_TIME = 278730197L;
+ private static boolean sExpectedPresentationTimeFlagValue;
+ static {
+ sExpectedPresentationTimeFlagValue = expectedPresentationTimeReadOnly();
+ }
+
private static class AnimationState {
boolean animationClockLocked;
long currentVsyncTimeMillis;
@@ -108,12 +113,12 @@ public class AnimationUtils {
* @hide
*/
@TestApi
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static void lockAnimationClock(long vsyncMillis, long expectedPresentationTimeNanos) {
AnimationState state = sAnimationState.get();
state.animationClockLocked = true;
state.currentVsyncTimeMillis = vsyncMillis;
- if (!expectedPresentationTimeApi()) {
+ if (!sExpectedPresentationTimeFlagValue) {
state.mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
}
}
@@ -158,9 +163,9 @@ public class AnimationUtils {
* @return the expected presentation time of a frame in the
* {@link System#nanoTime()} time base.
*/
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static long getExpectedPresentationTimeNanos() {
- if (!expectedPresentationTimeApi()) {
+ if (!sExpectedPresentationTimeFlagValue) {
return SystemClock.uptimeMillis();
}
@@ -176,7 +181,7 @@ public class AnimationUtils {
* @return the expected presentation time of a frame in the
* {@link SystemClock#uptimeMillis()} time base.
*/
- @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_API)
+ @FlaggedApi(FLAG_EXPECTED_PRESENTATION_TIME_READ_ONLY)
public static long getExpectedPresentationTimeMillis() {
return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS;
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index a40ff643379a..96574f5c959e 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -3392,7 +3392,7 @@ public final class AutofillManager {
return false;
}
for (String hint : hints) {
- if (Objects.equals(hint, View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
+ if (hint != null && hint.startsWith(View.AUTOFILL_HINT_CREDENTIAL_MANAGER)) {
return true;
}
}
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 42b3e38b544f..57011e879454 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -364,14 +364,6 @@ public final class ContentCaptureManager {
"enable_content_protection_receiver";
/**
- * Sets the size of the app blocklist for the content protection flow.
- *
- * @hide
- */
- public static final String DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE =
- "content_protection_apps_blocklist_size";
-
- /**
* Sets the size of the in-memory ring buffer for the content protection flow.
*
* @hide
@@ -440,8 +432,6 @@ public final class ContentCaptureManager {
/** @hide */
public static final boolean DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER = false;
/** @hide */
- public static final int DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE = 5000;
- /** @hide */
public static final int DEFAULT_CONTENT_PROTECTION_BUFFER_SIZE = 150;
/** @hide */
public static final List<List<String>> DEFAULT_CONTENT_PROTECTION_REQUIRED_GROUPS =
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index 5c86feb3c22c..f6ee061fdd89 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -13,3 +13,10 @@ flag {
description: "If true, content protection groups config will be parsed."
bug: "302187922"
}
+
+flag {
+ name: "setting_ui_enabled"
+ namespace: "content_protection"
+ description: "If true, content protection setting ui is displayed in Settings > Privacy & Security > More security & privacy."
+ bug: "305792348"
+}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index fd9689013af3..2b08eebc0aa0 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -22,6 +22,14 @@ flag {
}
flag {
+ name: "expected_presentation_time_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for using expected presentation time of the Choreographer"
+ bug: "278730197"
+ is_fixed_read_only: true
+}
+
+flag {
name: "set_frame_rate_callback"
namespace: "core_graphics"
description: "Enable the `setFrameRate` callback"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index f9d8b083ee9c..1bc735397115 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -172,7 +172,6 @@ public interface ImeTracker {
PHASE_CLIENT_HANDLE_HIDE_INSETS,
PHASE_CLIENT_APPLY_ANIMATION,
PHASE_CLIENT_CONTROL_ANIMATION,
- PHASE_CLIENT_DISABLED_USER_ANIMATION,
PHASE_CLIENT_COLLECT_SOURCE_CONTROLS,
PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
PHASE_CLIENT_REQUEST_IME_SHOW,
@@ -292,9 +291,6 @@ public interface ImeTracker {
/** Started the IME window insets show animation. */
int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
- /** Checked that the IME is controllable. */
- int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_ANIMATION;
-
/** Collecting insets source controls. */
int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 7eee33f7f617..9f0b31bdc960 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -770,7 +770,7 @@ public final class TextAppearanceInfo implements Parcelable {
}
/**
- * Set the font variation settings. Returns null if no variation is specified.
+ * Set the font variation settings. Set {@code null} if no variation is specified.
*
* @see Paint#getFontVariationSettings()
*/
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 103725da9af0..f19a2f961391 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -685,8 +685,9 @@ public class RemoteViews implements Parcelable, Filter {
return false;
}
+ /** See {@link RemoteViews#visitUris(Consumer)}. **/
public void visitUris(@NonNull Consumer<Uri> visitor) {
- // Nothing to visit by default
+ // Nothing to visit by default.
}
}
@@ -761,9 +762,11 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
- * Note all {@link Uri} that are referenced internally, with the expectation
- * that Uri permission grants will need to be issued to ensure the recipient
- * of this object is able to render its contents.
+ * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission
+ * grants will need to be issued to ensure the recipient of this object is able to render its
+ * contents.
+ * See b/281044385 for more context and examples about what happens when this isn't done
+ * correctly.
*
* @hide
*/
@@ -1088,6 +1091,13 @@ public class RemoteViews implements Parcelable, Filter {
public String getUniqueKey() {
return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (RemoteViews remoteViews : mList) {
+ remoteViews.visitUris(visitor);
+ }
+ }
}
/**
@@ -1289,6 +1299,12 @@ public class RemoteViews implements Parcelable, Filter {
public String getUniqueKey() {
return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId);
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ RemoteCollectionItems items = getCollectionItemsFromFuture(mItemsFuture);
+ items.visitUris(visitor);
+ }
}
private class SetRemoteViewsAdapterIntent extends Action {
@@ -1359,6 +1375,13 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs. This may require adding a dedicated
+ // visitUris method in the Intent class, since it can contain other intents. Otherwise,
+ // the basic thing to do here would be just visitor.accept(intent.getData()).
+ }
}
/**
@@ -1434,6 +1457,11 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return SET_ON_CLICK_RESPONSE_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+ }
}
/**
@@ -1504,6 +1532,11 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
}
+
+ @Override
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
+ // TODO(b/281044385): Maybe visit intent URIs in the RemoteResponse.
+ }
}
/** @hide **/
@@ -2063,6 +2096,7 @@ public class RemoteViews implements Parcelable, Filter {
final Icon icon = (Icon) getParameterValue(null);
if (icon != null) visitIconUri(icon, visitor);
break;
+ // TODO(b/281044385): Should we do anything about type BUNDLE?
}
}
}
@@ -2812,7 +2846,7 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
- public final void visitUris(@NonNull Consumer<Uri> visitor) {
+ public void visitUris(@NonNull Consumer<Uri> visitor) {
mNestedViews.visitUris(visitor);
}
}
@@ -7262,6 +7296,15 @@ public class RemoteViews implements Parcelable, Filter {
Math.max(mViewTypeCount, 1));
}
}
+
+ /**
+ * See {@link RemoteViews#visitUris(Consumer)}.
+ */
+ private void visitUris(@NonNull Consumer<Uri> visitor) {
+ for (RemoteViews view : mViews) {
+ view.visitUris(visitor);
+ }
+ }
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 17c82b63c443..6da6a64dc042 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -28,6 +28,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import android.R;
@@ -865,6 +866,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private final boolean mUseTextPaddingForUiTranslation;
private boolean mUseBoundsForWidth;
+ @Nullable private Paint.FontMetrics mMinimumFontMetrics;
@ViewDebug.ExportedProperty(category = "text")
@UnsupportedAppUsage
@@ -4901,6 +4903,58 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Set the minimum font metrics used for line spacing.
+ *
+ * <p>
+ * {@code null} is the default value. If {@code null} is set or left as default, the font
+ * metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)} is used.
+ *
+ * <p>
+ * The minimum meaning here is the minimum value of line spacing: maximum value of
+ * {@link Paint#ascent()}, minimum value of {@link Paint#descent()}.
+ *
+ * <p>
+ * By setting this value, each line will have minimum line spacing regardless of the text
+ * rendered. For example, usually Japanese script has larger vertical metrics than Latin script.
+ * By setting the metrics obtained by {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * for Japanese or leave it {@code null} if the TextView's locale or system locale is Japanese,
+ * the line spacing for Japanese is reserved if the TextView contains English text. If the
+ * vertical metrics of the text is larger than Japanese, for example Burmese, the bigger font
+ * metrics is used.
+ *
+ * @param minimumFontMetrics A minimum font metrics. Passing {@code null} for using the value
+ * obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ * @see #getMinimumFontMetrics()
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
+ mMinimumFontMetrics = minimumFontMetrics;
+ }
+
+ /**
+ * Get the minimum font metrics used for line spacing.
+ *
+ * @see #setMinimumFontMetrics(Paint.FontMetrics)
+ * @see Layout#getMinimumFontMetrics()
+ * @see Layout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see StaticLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ * @see DynamicLayout.Builder#setMinimumFontMetrics(Paint.FontMetrics)
+ *
+ * @return a minimum font metrics. {@code null} for using the value obtained by
+ * {@link Paint#getFontMetricsForLocale(Paint.FontMetrics)}
+ */
+ @Nullable
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public Paint.FontMetrics getMinimumFontMetrics() {
+ return mMinimumFontMetrics;
+ }
+
+ /**
* @return whether fallback line spacing is enabled, {@code true} by default
*
* @see #setFallbackLineSpacing(boolean)
@@ -9854,7 +9908,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
outAttrs.setInitialSurroundingText(mText);
outAttrs.contentMimeTypes = getReceiveContentMimeTypes();
-
+ if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()
+ && isAutoHandwritingEnabled()) {
+ outAttrs.setStylusHandwritingEnabled(true);
+ }
ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>();
gestures.add(SelectGesture.class);
gestures.add(SelectRangeGesture.class);
@@ -10680,7 +10737,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (hintBoring == UNKNOWN_BORING) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(),
+ mMinimumFontMetrics, mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -10729,7 +10787,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(mEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10793,12 +10852,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mLineBreakStyle, mLineBreakWordStyle))
.setUseBoundsForWidth(mUseBoundsForWidth)
.setEllipsize(getKeyListener() == null ? effectiveEllipsize : null)
- .setEllipsizedWidth(ellipsisWidth);
+ .setEllipsizedWidth(ellipsisWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
result = builder.build();
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -10812,7 +10872,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, null, wantWidth,
isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10826,7 +10886,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
wantWidth,
null,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
if (useSaved) {
@@ -10838,7 +10899,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
wantWidth, alignment, mSpacingMult, mSpacingAdd,
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth, isFallbackLineSpacingForBoringLayout(),
- mUseBoundsForWidth);
+ mUseBoundsForWidth, mMinimumFontMetrics);
} else {
result = new BoringLayout(
mTransformed,
@@ -10852,7 +10913,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ellipsisWidth,
effectiveEllipsize,
boring,
- mUseBoundsForWidth);
+ mUseBoundsForWidth,
+ mMinimumFontMetrics);
}
}
}
@@ -10871,7 +10933,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE)
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
if (shouldEllipsize) {
builder.setEllipsize(effectiveEllipsize)
.setEllipsizedWidth(ellipsisWidth);
@@ -10999,7 +11062,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics, mBoring);
if (boring != null) {
mBoring = boring;
}
@@ -11039,7 +11102,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (hintDes < 0) {
hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
- isFallbackLineSpacingForBoringLayout(), mHintBoring);
+ isFallbackLineSpacingForBoringLayout(), mMinimumFontMetrics,
+ mHintBoring);
if (hintBoring != null) {
mHintBoring = hintBoring;
}
@@ -11251,7 +11315,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
.setTextDirection(getTextDirectionHeuristic())
.setLineBreakConfig(LineBreakConfig.getLineBreakConfig(
mLineBreakStyle, mLineBreakWordStyle))
- .setUseBoundsForWidth(mUseBoundsForWidth);
+ .setUseBoundsForWidth(mUseBoundsForWidth)
+ .setMinimumFontMetrics(mMinimumFontMetrics);
final StaticLayout layout = layoutBuilder.build();
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 73778900399c..4705dc5db90b 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -8,6 +8,14 @@ flag {
}
flag {
+ name: "defer_display_updates"
+ namespace: "window_manager"
+ description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
+ bug: "259220649"
+ is_fixed_read_only: true
+}
+
+flag {
name: "close_to_square_config_includes_status_bar"
namespace: "windowing_frontend"
description: "On close to square display, when necessary, configuration includes status bar"
@@ -15,10 +23,10 @@ flag {
}
flag {
- name: "dimmer_refactor"
+ name: "introduce_smoother_dimmer"
namespace: "windowing_frontend"
description: "Refactor dim to fix flickers"
- bug: "281632483,295291019"
+ bug: "295291019"
is_fixed_read_only: true
}
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index 4e7bfe50cd30..71bbccb3d989 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -259,7 +259,7 @@ public class PlatLogoActivity extends Activity {
}
return true;
}
- return false;
+ return super.onKeyDown(keyCode,event);
}
@Override
@@ -268,7 +268,7 @@ public class PlatLogoActivity extends Activity {
stopWarp();
return true;
}
- return false;
+ return super.onKeyUp(keyCode,event);
}
private void startWarp() {
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index b1d22e069d9d..77e150239803 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,11 +81,6 @@ public class SystemUiSystemPropertiesFlags {
public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag(
"persist.sysui.notification.propagate_channel_updates_to_conversations");
- // TODO: b/291907312 - remove feature flags
- /** Gating the NMS->NotificationAttentionHelper buzzBeepBlink refactor */
- public static final Flag ENABLE_ATTENTION_HELPER_REFACTOR = devFlag(
- "persist.debug.sysui.notification.enable_attention_helper_refactor");
-
// TODO b/291899544: for released flags, use resource config values
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
index f77e96286bea..65a2f4be9901 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl
@@ -46,4 +46,5 @@ oneway interface IInputMethodPrivilegedOperations {
in @nullable ImeTracker.Token statsToken);
void onStylusHandwritingReady(int requestId, int pid);
void resetStylusHandwriting(int requestId);
+ void switchKeyboardLayoutAsync(int direction);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 30ebbe2bb111..792388dca339 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -431,4 +431,20 @@ public final class InputMethodPrivilegedOperations {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Calls {@link IInputMethodPrivilegedOperations#switchKeyboardLayoutAsync(int)}.
+ */
+ @AnyThread
+ public void switchKeyboardLayoutAsync(int direction) {
+ final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
+ if (ops == null) {
+ return;
+ }
+ try {
+ ops.switchKeyboardLayoutAsync(direction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 6e836e077e44..e6b036cfaa19 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -40,6 +40,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA;
@@ -57,6 +58,9 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD;
@@ -273,7 +277,13 @@ public class InteractionJankMonitor {
public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82;
- private static final int LAST_CUJ = CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83;
+
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
+ public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
+ public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+
+ private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
private static final int NO_STATSD_LOGGING = -1;
// Used to convert CujType to InteractionType enum value for statsd logging.
@@ -366,6 +376,13 @@ public class InteractionJankMonitor {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] =
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
}
private static class InstanceHolder {
@@ -468,6 +485,10 @@ public class InteractionJankMonitor {
CUJ_IME_INSETS_SHOW_ANIMATION,
CUJ_IME_INSETS_HIDE_ANIMATION,
CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
+ CUJ_LAUNCHER_UNFOLD_ANIM,
+ CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
+ CUJ_PREDICTIVE_BACK_CROSS_TASK,
+ CUJ_PREDICTIVE_BACK_HOME,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -1101,6 +1122,14 @@ public class InteractionJankMonitor {
return "IME_INSETS_HIDE_ANIMATION";
case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER:
return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER";
+ case CUJ_LAUNCHER_UNFOLD_ANIM:
+ return "LAUNCHER_UNFOLD_ANIM";
+ case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY:
+ return "PREDICTIVE_BACK_CROSS_ACTIVITY";
+ case CUJ_PREDICTIVE_BACK_CROSS_TASK:
+ return "PREDICTIVE_BACK_CROSS_TASK";
+ case CUJ_PREDICTIVE_BACK_HOME:
+ return "PREDICTIVE_BACK_HOME";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 406505517bcc..82367834f93d 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -108,4 +108,5 @@ interface ILockSettings {
boolean removeWeakEscrowToken(long handle, int userId);
boolean isWeakEscrowTokenActive(long handle, int userId);
boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
+ void unlockUserKeyIfUnsecured(int userId);
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d5b8f62aaf2b..a3e27062fa7b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1933,8 +1933,23 @@ public class LockPatternUtils {
}
}
+ /**
+ * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
+ * doesn't have an LSKF.
+ * <p>
+ * Whether the storage has been unlocked can be determined by
+ * {@link StorageManager#isUserKeyUnlocked()}.
+ *
+ * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
+ *
+ * @param userId the ID of the user whose storage to unlock
+ */
public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
- getLockSettingsInternal().unlockUserKeyIfUnsecured(userId);
+ try {
+ getLockSettings().unlockUserKeyIfUnsecured(userId);
+ } catch (RemoteException re) {
+ re.rethrowFromSystemServer();
+ }
}
public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index 6063c90d6ab9..8114e1fd3bb0 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -60,17 +60,6 @@ public abstract class LockSettingsInternal {
public abstract void onThirdPartyAppsStarted();
/**
- * Unlocks the credential-encrypted storage for the given user if the user is not secured, i.e.
- * doesn't have an LSKF.
- * <p>
- * This doesn't throw an exception on failure; whether the storage has been unlocked can be
- * determined by {@link StorageManager#isUserKeyUnlocked()}.
- *
- * @param userId the ID of the user whose storage to unlock
- */
- public abstract void unlockUserKeyIfUnsecured(@UserIdInt int userId);
-
- /**
* Creates the locksettings state for a new user.
* <p>
* This includes creating a synthetic password and protecting it with an empty LSKF.
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 34ef7b3254a4..5b95ee79f330 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -85,6 +85,18 @@ static jboolean android_hardware_OverlayProperties_supportMixedColorSpaces(JNIEn
return false;
}
+static jlong android_hardware_OverlayProperties_createDefault(JNIEnv* env, jobject thiz) {
+ gui::OverlayProperties* overlayProperties = new gui::OverlayProperties;
+ gui::OverlayProperties::SupportedBufferCombinations combination;
+ combination.pixelFormats = {HAL_PIXEL_FORMAT_RGBA_8888};
+ combination.standards = {HAL_DATASPACE_BT709};
+ combination.transfers = {HAL_DATASPACE_TRANSFER_SRGB};
+ combination.ranges = {HAL_DATASPACE_RANGE_FULL};
+ overlayProperties->combinations.emplace_back(combination);
+ overlayProperties->supportMixedColorSpaces = true;
+ return reinterpret_cast<jlong>(overlayProperties);
+}
+
// ----------------------------------------------------------------------------
// Serialization
// ----------------------------------------------------------------------------
@@ -150,6 +162,7 @@ static const JNINativeMethod gMethods[] = {
(void*) android_hardware_OverlayProperties_write },
{ "nReadOverlayPropertiesFromParcel", "(Landroid/os/Parcel;)J",
(void*) android_hardware_OverlayProperties_read },
+ {"nCreateDefault", "()J", (void*) android_hardware_OverlayProperties_createDefault },
};
// clang-format on
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index f97d41b6a122..262f5e8e761e 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -42,13 +42,6 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
return NULL;
}
- // b/274058082: Pass a copy of the key character map to avoid concurrent
- // access
- std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
- if (map != nullptr) {
- map = std::make_shared<KeyCharacterMap>(*map);
- }
-
ScopedLocalRef<jstring> descriptorObj(env,
env->NewStringUTF(deviceInfo.getIdentifier().descriptor.c_str()));
if (!descriptorObj.get()) {
@@ -67,9 +60,14 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
? layoutInfo->layoutType.c_str()
: NULL));
+ std::shared_ptr<KeyCharacterMap> map = deviceInfo.getKeyCharacterMap();
+ std::unique_ptr<KeyCharacterMap> mapCopy;
+ if (map != nullptr) {
+ mapCopy = std::make_unique<KeyCharacterMap>(*map);
+ }
ScopedLocalRef<jobject> kcmObj(env,
android_view_KeyCharacterMap_create(env, deviceInfo.getId(),
- map));
+ std::move(mapCopy)));
if (!kcmObj.get()) {
return NULL;
}
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 7f69e22fb0d1..a79e37afd4cd 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -48,7 +48,7 @@ static struct {
class NativeKeyCharacterMap {
public:
- NativeKeyCharacterMap(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map)
+ NativeKeyCharacterMap(int32_t deviceId, std::unique_ptr<KeyCharacterMap> map)
: mDeviceId(deviceId), mMap(std::move(map)) {}
~NativeKeyCharacterMap() {
@@ -58,16 +58,18 @@ public:
return mDeviceId;
}
- inline const std::shared_ptr<KeyCharacterMap> getMap() const { return mMap; }
+ inline const std::unique_ptr<KeyCharacterMap>& getMap() const {
+ return mMap;
+ }
private:
int32_t mDeviceId;
- std::shared_ptr<KeyCharacterMap> mMap;
+ std::unique_ptr<KeyCharacterMap> mMap;
};
jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
- const std::shared_ptr<KeyCharacterMap> kcm) {
- NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, kcm);
+ std::unique_ptr<KeyCharacterMap> kcm) {
+ NativeKeyCharacterMap* nativeMap = new NativeKeyCharacterMap(deviceId, std::move(kcm));
if (!nativeMap) {
return nullptr;
}
@@ -91,7 +93,7 @@ static jlong nativeReadFromParcel(JNIEnv *env, jobject clazz, jobject parcelObj)
return 0;
}
- std::shared_ptr<KeyCharacterMap> kcm = nullptr;
+ std::unique_ptr<KeyCharacterMap> kcm;
// Check if map is a null character map
if (parcel->readBool()) {
kcm = KeyCharacterMap::readFromParcel(parcel);
@@ -99,7 +101,7 @@ static jlong nativeReadFromParcel(JNIEnv *env, jobject clazz, jobject parcelObj)
return 0;
}
}
- NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, kcm);
+ NativeKeyCharacterMap* map = new NativeKeyCharacterMap(deviceId, std::move(kcm));
return reinterpret_cast<jlong>(map);
}
@@ -230,9 +232,9 @@ static jobjectArray nativeGetEvents(JNIEnv *env, jobject clazz, jlong ptr,
}
static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) {
- const std::shared_ptr<KeyCharacterMap>& map1 =
+ const std::unique_ptr<KeyCharacterMap>& map1 =
(reinterpret_cast<NativeKeyCharacterMap*>(ptr1))->getMap();
- const std::shared_ptr<KeyCharacterMap>& map2 =
+ const std::unique_ptr<KeyCharacterMap>& map2 =
(reinterpret_cast<NativeKeyCharacterMap*>(ptr2))->getMap();
if (map1 == nullptr || map2 == nullptr) {
return map1 == map2;
diff --git a/core/jni/android_view_KeyCharacterMap.h b/core/jni/android_view_KeyCharacterMap.h
index be0335380f87..a8aabd1d464a 100644
--- a/core/jni/android_view_KeyCharacterMap.h
+++ b/core/jni/android_view_KeyCharacterMap.h
@@ -25,7 +25,7 @@ namespace android {
/* Creates a KeyCharacterMap object from the given information. */
extern jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
- const std::shared_ptr<KeyCharacterMap> kcm);
+ std::unique_ptr<KeyCharacterMap> kcm);
} // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 178c0d0d95be..98335988459a 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -126,7 +126,7 @@ static struct {
jfieldID height;
jfieldID xDpi;
jfieldID yDpi;
- jfieldID refreshRate;
+ jfieldID peakRefreshRate;
jfieldID vsyncRate;
jfieldID appVsyncOffsetNanos;
jfieldID presentationDeadlineNanos;
@@ -1232,7 +1232,7 @@ static jobject convertDisplayModeToJavaObject(JNIEnv* env, const ui::DisplayMode
env->SetFloatField(object, gDisplayModeClassInfo.xDpi, config.xDpi);
env->SetFloatField(object, gDisplayModeClassInfo.yDpi, config.yDpi);
- env->SetFloatField(object, gDisplayModeClassInfo.refreshRate, config.refreshRate);
+ env->SetFloatField(object, gDisplayModeClassInfo.peakRefreshRate, config.peakRefreshRate);
env->SetFloatField(object, gDisplayModeClassInfo.vsyncRate, config.vsyncRate);
env->SetLongField(object, gDisplayModeClassInfo.appVsyncOffsetNanos, config.appVsyncOffset);
env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
@@ -2396,7 +2396,7 @@ int register_android_view_SurfaceControl(JNIEnv* env)
gDisplayModeClassInfo.height = GetFieldIDOrDie(env, modeClazz, "height", "I");
gDisplayModeClassInfo.xDpi = GetFieldIDOrDie(env, modeClazz, "xDpi", "F");
gDisplayModeClassInfo.yDpi = GetFieldIDOrDie(env, modeClazz, "yDpi", "F");
- gDisplayModeClassInfo.refreshRate = GetFieldIDOrDie(env, modeClazz, "refreshRate", "F");
+ gDisplayModeClassInfo.peakRefreshRate = GetFieldIDOrDie(env, modeClazz, "peakRefreshRate", "F");
gDisplayModeClassInfo.vsyncRate = GetFieldIDOrDie(env, modeClazz, "vsyncRate", "F");
gDisplayModeClassInfo.appVsyncOffsetNanos =
GetFieldIDOrDie(env, modeClazz, "appVsyncOffsetNanos", "J");
diff --git a/core/proto/android/nfc/Android.bp b/core/proto/android/nfc/Android.bp
deleted file mode 100644
index 6a62c917f240..000000000000
--- a/core/proto/android/nfc/Android.bp
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-filegroup {
- name: "srcs_nfc_proto",
- srcs: [
- "*.proto",
- ],
-}
-
-// Will be statically linked by `framework-nfc`.
-java_library {
- name: "nfc-proto-java-gen",
- installable: false,
- proto: {
- type: "stream",
- include_dirs: [
- "external/protobuf/src",
- ],
- },
- srcs: [
- ":srcs_nfc_proto",
- ],
- sdk_version: "current",
- min_sdk_version: "current",
-}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b73a7651c567..6daa5b934284 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5668,7 +5668,8 @@
android:description="@string/permdesc_deliverCompanionMessages"
android:protectionLevel="normal" />
- <!-- @hide @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
+ <!-- @hide @FlaggedApi("android.companion.flags.companion_transport_apis")
+ @SystemApi(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES)
Allows an application to send and receive messages via CDM transports.
-->
<permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index cd951cbdde1c..50372395a1dc 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programme wat batterykrag gebruik"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toeganklikheidgebruik"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skerm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans batterykrag"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> programme gebruik tans batterykrag"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik vir besonderhede oor battery- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Laat ’n metgeselapp toe om voorgronddienste van agtergrond af te begin"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoon is beskikbaar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoon is geblokkeer"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan nie na skerm weerspieël nie"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik ’n ander kabel en probeer weer"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel steun dalk nie skerms nie"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Jou USB-C-kabel koppel dalk nie behoorlik aan skerms nie"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is aan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruik tans albei skerms om inhoud te wys"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 42ba598502d5..9a8bb6233172 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ባትሪ በመፍጀት ላይ ያሉ መተግበሪያዎች"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ማጉላት"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"የተደራሽነት አጠቃቀም"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ማሳያ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ባትሪ እየተጠቀመ ነው"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> መተግበሪያዎች ባትሪ እየተጠቀሙ ነው"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"በባትሪ እና ውሂብ አጠቃቀም ላይ ዝርዝሮችን ለማግኘት መታ ያድርጉ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"አጃቢ መተግበሪያ ከዳራ የፊት አገልግሎቶችን እንዲጀምር ያስችላል።"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ማይክሮፎን ይገኛል"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ማይክሮፎን ታግዷል"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ወደ ማሳያ ማንጸባረቅ አልተቻለም"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"የተለየ ገመድ ይጠቀሙ እና እንደገና ይሞክሩ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ገመድ ማሳያዎችን ላይደግፍ ይችላል"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"የእርስዎ USB-C ገመድ ከማሳያዎች ጋር በትክክል ላይገናኝ ይችላል"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ገፅ በርቷል"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ይዘትን ለማሳየት ሁለቱንም ማሳያዎች እየተጠቀመ ነው"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 68d7ff4731ac..f6d7c647c84f 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -294,6 +294,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"التطبيقات التي تستهلك البطارية"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"التكبير"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"استخدام \"أدوات تسهيل الاستخدام\""</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"الشاشة"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"يستخدم تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> البطارية"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"تستخدم <xliff:g id="NUMBER">%1$d</xliff:g> من التطبيقات البطارية"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"انقر للحصول على تفاصيل حول البطارية واستخدام البيانات"</string>
@@ -2337,6 +2338,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"يسمح هذا الإذن للتطبيق المصاحب ببدء الخدمات التي تعمل في المقدّمة من الخلفية."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"الميكروفون متاح."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"تم حظر الميكروفون."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"يتعذّر إجراء نسخ مطابق لمحتوى جهازك إلى الشاشة"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"يُرجى استخدام كابل آخر وإعادة المحاولة."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"قد لا يتوافق الكابل مع الشاشات"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏قد لا يتم توصيل الكابل المزوَّد بمنفذ USB-C بالشاشات بشكل صحيح."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‏ميزة Dual Screen مفعّلة"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"يستخدم \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" كلتا الشاشتين لعرض المحتوى."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index ba3e7567f103..df4f28a0eac8 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"বেটাৰী খৰচ কৰা এপ্‌সমূহ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বিবৰ্ধন"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"সাধ্য সুবিধাৰ ব্যৱহাৰ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ডিছপ্লে’"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টা এপে বেটাৰী ব্যৱহাৰ কৰি আছে"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"বেটাৰী আৰু ডেটাৰ ব্যৱহাৰৰ বিষয়ে সবিশেষ জানিবলৈ টিপক"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"এটা সহযোগী এপক নেপথ্যৰ পৰা অগ্ৰভূমি সেৱাসমূহ আৰম্ভ কৰিবলৈ দিয়ে।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্ৰ’ফ’নটো উপলব্ধ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্ৰ’ফ’নটো অৱৰোধ কৰি থোৱা আছে"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"সংযুক্ত ডিছপ্লে’ উপলব্ধ নহয়"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য এডাল কে’বল ব্যৱহাৰ কৰি পুনৰ চেষ্টা কৰক"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কে’বলে ডিছপ্লে’ সমৰ্থন নকৰিবও পাৰে"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপোনাৰ USB-C কে’বল ডিছপ্লে’ৰ সৈতে সঠিকভাৱে সংযোগ নহ’বও পাৰে"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen অন আছে"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>এ সমল দেখুৱাবলৈ দুয়োখন ডিছপ্লে’ ব্যৱহাৰ কৰি আছে"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 4124dfa29da0..8bfd8b57d229 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareyadan istifadə edən tətbiqlər"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Böyütmə"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Əlçatımlılıq istifadəsi"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> batareyadan istifadə edir"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> tətbiq batareyadan istifadə edir"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya və data istifadəsi haqqında ətraflı məlumat üçün klikləyin"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Kompanyon tətbiqinə ön fon xidmətlərini arxa fondan başlatmaq icazəsi verir."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon əlçatandır"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon blok edilib"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeydə əks etdirmək olmur"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Başqa kabel istifadə edin və yenidən cəhd edin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeyləri dəstəkləməyə bilər"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabeli displeylərə düzgün qoşulmaya bilər"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"İkili ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"İkili ekran aktivdir"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> məzmunu göstərmək üçün hər iki displeydən istifadə edir"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 3fd8dcb2927a..48b5c02c6286 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korišćenje Pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikacije (<xliff:g id="NUMBER">%1$d</xliff:g>) koriste bateriju"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o bateriji i potrošnji podataka"</string>
@@ -314,7 +315,7 @@
<string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Muzika i zvuk"</string>
<string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"pristup muzici i audio sadržaju na uređaju"</string>
<string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Slike i video snimci"</string>
- <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i video snimcima na uređaju"</string>
+ <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i videima na uređaju"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Fizičke aktivnosti"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da pokrene usluge u prvom planu iz pozadine."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Preslikavanje na ekran nije moguće"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrebite drugi kabl i probajte ponovo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl ne podržava ekrane"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se ne povezuje pravilno sa ekranima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 715aad33e5c9..12effa0c6485 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Праграмы, якія выкарыстоўваюць акумулятар"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Павелічэнне"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Выкарыстанне спецыяльных магчымасцей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дысплэй"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> выкарыстоўвае акумулятар"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Наступная колькасць праграм выкарыстоўваюць акумулятар: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Дакраніцеся, каб даведацца пра выкарыстанне трафіка і акумулятара"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Спадарожная праграма зможа запускаць актыўныя сэрвісы з фонавага рэжыму."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрафон даступны"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрафон заблакіраваны"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не ўдалося прадубліраваць змесціва на дысплэі"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Паспрабуйце скарыстаць іншы кабель"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Магчыма, кабель несумяшчальны з дысплэямі"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Магчыма, кабель USB-C не падыходзіць да дысплэяў"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Уключана функцыя Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Праграма \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" выкарыстоўвае абодва экраны для паказу змесціва"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 9dd971c14b81..d18e4bc5f00e 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, използващи батерията"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Използване на услугите за достъпност"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва батерията"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> приложения използват батерията"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Докоснете за информация относно използването на батерията и преноса на данни"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Разрешава на дадено придружаващо приложение да стартира услуги на преден план, докато се изпълнява на заден план."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонът е налице"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонът е блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се копира огледално на дисплея"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Използвайте друг кабел и опитайте отново"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелът не поддържа дисплеи"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелът ви може да не се свързва правилно с дисплеи"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функцията Dual Screen е включена"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> използва и двата екрана, за да показва съдържание"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 6f879e42b534..859f37d026aa 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"কিছু অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"বড় করে দেখা"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"অ্যাক্সেসিবিলিটি সংক্রান্ত ব্যবহার"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ডিসপ্লে"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> অ্যাপটি ব্যাটারি ব্যবহার করছে"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>টি অ্যাপ ব্যাটারি ব্যবহার করছে"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ব্যাটারি এবং ডেটার ব্যবহারের বিশদ বিবরণের জন্য ট্যাপ করুন"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"কম্প্যানিয়ন অ্যাপকে, ব্যাকগ্রাউন্ড থেকে ফোরগ্রাউন্ড পরিষেবা চালু করার অনুমতি দেয়।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"মাইক্রোফোন উপলভ্য আছে"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"মাইক্রোফোন ব্লক করা হয়েছে"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ডিসপ্লে মিরর করা যাচ্ছে না"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"অন্য কোনও কেবল ব্যবহার করে আবার চেষ্টা করুন"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"কেবল, ডিসপ্লের সাথে কাজ নাও করতে পারে"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"আপনার USB-C কেবল, ডিসপ্লেতে সঠিকভাবে কানেক্ট নাও হতে পারে"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen চালু করা আছে"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"কন্টেন্ট দেখানোর জন্য <xliff:g id="APP_NAME">%1$s</xliff:g> দুটি ডিসপ্লে ব্যবহার করছে"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b0f19053eda7..8e9fe9c75bec 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije koje troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Uvećavanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Korištenje pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> troši bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje troše bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite za detalje o potrošnji baterije i prijenosa podataka"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Dozvoljava pratećoj aplikaciji da iz pozadine pokrene usluge u prvom planu."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nije moguće preslikati na ekran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabl i pokušajte ponovo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabl možda neće podržavati ekrane"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabl se možda neće pravilno povezati s ekranima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je uključen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> koristi oba ekrana za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index be95847849ba..dacdbf15baa5 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacions que consumeixen bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliació"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ús de les funcions d\'accessibilitat"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> està consumint bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacions estan consumint bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca per obtenir informació sobre l\'ús de dades i de bateria"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet que una aplicació complementària iniciï serveis en primer pla des d\'un segon pla."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micròfon està disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"És possible que el cable no sigui compatible amb pantalles"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"És possible que el teu cable USB-C no es connecti correctament a les pantalles"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La pantalla dual està activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> està utilitzant les dues pantalles per mostrar contingut"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 32cff76055bd..29e00fa4d212 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikace spotřebovávají baterii"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zvětšení"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využití přístupnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displej"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> využívá baterii"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikace (<xliff:g id="NUMBER">%1$d</xliff:g>) využívají baterii"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o využití baterie a dat"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje doprovodné aplikaci spouštět z pozadí služby v popředí."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupný"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je zablokován"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nelze zrcadlit na displej"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použijte jiný kabel a zkuste to znovu"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možná nepodporuje displeje"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Váš kabel USB-C se možná nedokáže správně připojit k displejům"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkce Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> používá k zobrazení obsahu oba displeje"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 75bf3659eef7..87703cdb2fd9 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps, der bruger batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørrelse"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Brug af hjælpefunktioner"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skærm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps bruger batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryk for at se info om batteri- og dataforbrug"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillader, at en medfølgende app kan starte tjenester i forgrunden via tilladelser til tjenester i baggrunden."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgængelig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokeret"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det er ikke muligt at spejle til skærmen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Brug et andet kabel, og prøv igen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablet understøtter muligvis ikke skærme"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dit USB-C-kabel kan muligvis ikke sluttes korrekt til skærmene"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er aktiveret"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruger begge skærme til at vise indhold"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index e087fe2e723d..3faf32864c74 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Strom verbrauchende Apps"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergrößerung"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Nutzung der Bedienungshilfen"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> verbraucht Strom"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> Apps verbrauchen Strom"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Für Details zur Akku- und Datennutzung tippen"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ermöglicht einer Companion-App, Dienste im Vordergrund aus dem Hintergrund zu starten."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon ist verfügbar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon ist blockiert"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kann nicht auf das Display gespiegelt werden"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Verwende ein anderes Kabel und versuch es noch einmal"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel unterstützt eventuell keine Bildschirme"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Dein USB-C-Kabel ist möglicherweise nicht zum Verbinden von Bildschirmen geeignet"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ist aktiviert"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> nutzt zum Anzeigen von Inhalten beide Displays"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 9b713c99a17e..af53ddfcfee8 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Εφαρμογές που καταναλώνουν μπαταρία"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Μεγιστοποίηση"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Χρήση προσβασιμότητας"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Οθόνη"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί μπαταρία"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> εφαρμογές χρησιμοποιούν μπαταρία"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Πατήστε για λεπτομέρειες σχετικά με τη χρήση μπαταρίας και δεδομένων"</string>
@@ -1376,7 +1377,7 @@
<string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Εντοπίστηκε αναλογικό αξεσουάρ ήχου"</string>
<string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Η συνδεδεμένη συσκευή δεν είναι συμβατή με αυτό το τηλέφωνο. Πατήστε για να μάθετε περισσότερα."</string>
<string name="adb_active_notification_title" msgid="408390247354560331">"Συνδέθηκε ο εντοπ. σφαλμ. USB"</string>
- <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ./διόρθ. σφαλμ. USB"</string>
+ <string name="adb_active_notification_message" msgid="5617264033476778211">"Πατήστε για απενεργ. εντοπ. σφαλμ. USB"</string>
<string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Επιλογή για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
<string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Συνδέθηκε ο ασύρματος εντοπισμός σφαλμάτων"</string>
<string name="adbwifi_active_notification_message" msgid="930987922852867972">"Πατήστε, για να απενεργοποιήσετε τον ασύρματο εντοπισμό σφαλμάτων"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Επιτρέπει σε μια συνοδευτική εφαρμογή να εκκινεί υπηρεσίες στο προσκήνιο από το παρασκήνιο."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Το μικρόφωνο είναι διαθέσιμο"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Το μικρόφωνο έχει αποκλειστεί"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Δεν είναι δυνατός ο κατοπτρισμός στην οθόνη"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Χρησιμοποιήστε άλλο καλώδιο και δοκιμάστε ξανά"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Το καλώδιο μπορεί να μην υποστηρίζει οθόνες"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Το καλώδιο USB-C που έχετε ίσως να μην μπορεί να συνδεθεί σωστά σε οθόνες"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Διπλή οθόνη"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Η λειτουργία διπλής οθόνης είναι ενεργή"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Η εφαρμ. <xliff:g id="APP_NAME">%1$s</xliff:g> χρησιμοποιεί και τις 2 οθόνες για εμφάνιση περιεχ."</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index bc231f56c602..8917a8203285 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 29f4c78d9395..5a0ed859e441 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 5576054309dc..bcf0790bae59 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index ea95a513288e..7ebffc6895c7 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps consuming battery"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Magnification"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Accessibility usage"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using battery"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps are using battery"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tap for details on battery and data usage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Allows a companion app to start foreground services from background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microphone is available"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microphone is blocked"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Can\'t mirror to display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Please use a different cable and try again"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cable may not support displays"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Your USB-C cable may not connect to displays properly"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen is on"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> is using both displays to show content"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index c09e6ceabf14..b739768c2068 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎‎Apps consuming battery‎‏‎‎‏‎"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎‎Magnification‎‏‎‎‏‎"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎Accessibility usage‎‏‎‎‏‎"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎Display‎‏‎‎‏‎"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is using battery‎‏‎‎‏‎"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="NUMBER">%1$d</xliff:g>‎‏‎‎‏‏‏‎ apps are using battery‎‏‎‎‏‎"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‎Tap for details on battery and data usage‎‏‎‎‏‎"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎Allows a companion app to start foreground services from background.‎‏‎‎‏‎"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‎‎Microphone is available‎‏‎‎‏‎"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‎‏‎Microphone is blocked‎‏‎‎‏‎"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‏‎‏‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‏‎‎‎Can\'t mirror to display‎‏‎‎‏‎"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎Use a different cable and try again‎‏‎‎‏‎"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‎Cable may not support displays‎‏‎‎‏‎"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‏‎Your USB-C cable may not connect to displays properly‎‏‎‎‏‎"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‎‏‏‎‏‏‎‏‏‎Dual screen‎‏‎‎‏‎"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎Dual screen is on‎‏‎‎‏‎"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ is using both displays to show content‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 723f833190e6..ca9ff13550d3 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que consumen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está consumiendo batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps están consumiendo batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Presiona para obtener información sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede duplicar la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un cable diferente y vuelve a intentarlo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Es posible que el cable no admita pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Es posible que el cable USB-C no se conecte a las pantallas de manera adecuada"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 79c6e267e0b8..97b18887be34 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicaciones que consumen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidad"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando la batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicaciones están usando la batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para ver información detallada sobre el uso de datos y de la batería"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que una aplicación complementaria inicie servicios en primer plano desde el segundo plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"El micrófono está disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"El micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No se puede proyectar a la pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa otro cable y vuelve a intentarlo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"El cable puede no ser compatible con pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Puede que tu cable USB‑C no sea adecuado para conectarse a pantallas"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La función Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas pantallas para mostrar contenido"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 67c20f7d6ff0..4a8666ee1459 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Rakendused kasutavad akutoidet"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurendus"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Juurdepääsetavuse kasutus"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekraan"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab akutoidet"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> rakendust kasutab akutoidet"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Aku ja andmekasutuse üksikasjade nägemiseks puudutage"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lubab kaasrakendusel taustal käivitada esiplaanil olevaid teenuseid."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon on saadaval"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon on blokeeritud"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ei saa ekraanile peegeldada"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Kasutage teist kaablit ja proovige uuesti"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kaabel ei pruugi ekraane toetada"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Teie USB-C-kaabel ei pruugi ekraanidega õigesti ühendust luua"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screeni režiim on sisse lülitatud"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kasutab sisu kuvamiseks mõlemat ekraani"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 2150324d2fb1..a163ed8b754c 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Bateria kontsumitzen ari diren aplikazioak"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Lupa"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erabilerraztasun-hobespenak"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantaila"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ari da bateria erabiltzen"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikazio ari dira bateria erabiltzen"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Sakatu bateria eta datu-erabilerari buruzko xehetasunak ikusteko"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Aurreko planoko zerbitzuak atzeko planotik abiarazteko baimena ematen die aplikazio osagarriei."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Erabilgarri dago mikrofonoa"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Blokeatuta dago mikrofonoa"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ezin da islatu pantailan"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Erabili beste kable bat eta saiatu berriro"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Baliteke kablea pantailekin bateragarria ez izatea"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Baliteke USB-C kablea behar bezala ez konektatzea pantailetara"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen aktibatuta dago"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bi pantailak erabiltzen ari da edukia erakusteko"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index de1ba1a576df..c5a2ee62d891 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"برنامه‌های مصرف‌کننده باتری"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"درشت‌نمایی"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"کاربرد دسترس‌پذیری"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"نمایشگر"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال استفاده کردن از باتری است"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> برنامه درحال استفاده کردن از باتری هستند"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"برای جزئیات مربوط به مصرف باتری و داده، ضربه بزنید"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"به برنامه همراه اجازه می‌دهد سرویس‌های پیش‌نما را از پس‌زمینه راه‌اندازی کند."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"میکروفون دردسترس است"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"میکروفون مسدود شد"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"بازتاب دادن به نمایشگر ممکن نبود"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"از کابل دیگری استفاده کنید و دوباره امتحان کنید"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"شاید کابل از نمایشگر پشتیبانی نکند"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏کابل USB-C شما ممکن است به‌درستی به نمایشگرها وصل نشود"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"‫Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"‏‫Dual Screen روشن است"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"‫<xliff:g id="APP_NAME">%1$s</xliff:g> از هر دو نمایشگر برای نمایش محتوا استفاده می‌کند"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 22d48e2850ff..4a08b9006696 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkua kuluttavat sovellukset"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Suurennus"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Esteetön käyttö"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Näyttö"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää akkua."</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> sovellusta käyttää akkua."</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Katso lisätietoja akun ja datan käytöstä napauttamalla."</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Sallii kumppanisovelluksen aloittaa etualan palveluja taustalla."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni on käytettävissä"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni on estetty"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Näytön peilaaminen ei onnistu"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Käytä eri johtoa ja yritä uudelleen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Johto ei ehkä tue näyttöjä"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-johtosi ei ehkä yhdisty näyttöihin kunnolla"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Kaksoisnäyttö"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kaksoisnäyttö on päällä"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> käyttää molempia näyttöjä sisällön näyttämiseen"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 928bf7767183..1b637db1e0d7 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications qui sollicitent la pile"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Usage des fonctionnalités d\'accessibilité"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sollicite la pile"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications sollicitent la pile"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Touchez pour afficher des détails sur l\'utilisation de la pile et des données"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet à une application compagnon en arrière-plan de lancer des services d\'avant-plan."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Le microphone est accessible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Le microphone est bloqué"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossible de dupliquer l\'écran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un câble différent et réessayez"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble peut ne pas être compatible avec les écrans"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C peut ne pas se connecter correctement aux écrans"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen activé"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher le contenu"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d7d29a4c5ef7..4f8de0c36867 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Applications utilisant la batterie"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Agrandissement"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilisation de l\'accessibilité"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Écran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise la batterie"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> applications utilisent la batterie"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Appuyer pour obtenir des informations sur l\'utilisation de la batterie et des données"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Autorise une application associée à lancer des services de premier plan à partir de l\'arrière-plan."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Le micro est disponible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Le micro est bloqué"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Duplication impossible"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilisez un autre câble et réessayez"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Le câble n\'est peut-être pas compatible avec les écrans"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Votre câble USB-C n\'est peut-être pas connecté correctement à l\'écran"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Double écran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Double écran activé"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> utilise les deux écrans pour afficher du contenu"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index e2073fb7a2c0..b25cfcb82d1d 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicacións que consomen batería"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliación"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de accesibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Pantalla"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A aplicación <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo batería"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicacións están consumindo batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para obter información sobre o uso de datos e a batería"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que unha aplicación complementaria, desde un segundo plano, inicie servizos en primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O micrófono está dispoñible"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O micrófono está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Non se pode proxectar contido na pantalla"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Cambia de cable e téntao de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Pode que o cable non sexa compatible con pantallas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O teu cable USB-C pode que non se conecte ás pantallas de maneira adecuada"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen está activada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> está usando ambas as pantallas para mostrar contido"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 82b4a0df15c4..fae5ebc3279c 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ઍપ બૅટરીનો વપરાશ કરી રહ્યાં છે"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"મોટું કરવાની સુવિધા"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ઍક્સેસિબિલિટી વપરાશ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ડિસ્પ્લે"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> બૅટરીનો ઉપયોગ કરી રહ્યું છે"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ઍપ બૅટરીનો ઉપયોગ કરી રહ્યાં છે"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"બૅટરી અને ડેટા વપરાશ વિશેની વિગતો માટે ટૅપ કરો"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"સાથી ઍપને બૅકગ્રાઉન્ડમાંથી ફૉરગ્રાઉન્ડ સેવાઓ શરૂ કરવાની મંજૂરી આપે છે."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"માઇક્રોફોન ઉપલબ્ધ છે"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"માઇક્રોફોનને બ્લૉક કરવામાં આવ્યો છે"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ડિસ્પ્લે પર મિરર કરી શકાતું નથી"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"બીજા કોઈ કેબલનો ઉપયોગ કરો અને ફરી પ્રયાસ કરો"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"શક્ય છે કે કેબલ કદાચ ડિસ્પ્લેને સપોર્ટ ન આપતો હોય"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"તમારો USB-C કેબલ કદાચ ડિસ્પ્લે સાથે યોગ્ય રીતે કનેક્ટ ન થાય"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ચાલુ છે"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"કન્ટેન્ટ બતાવવા માટે <xliff:g id="APP_NAME">%1$s</xliff:g> બન્ને ડિસ્પ્લેનો ઉપયોગ કરી રહી છે"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 1f3a377e512b..d4eb380098f8 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"बैटरी की खपत करने वाले ऐप"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ज़ूम करने की सुविधा"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सुलभता सुविधाओं का इस्तेमाल"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिसप्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बैटरी का इस्तेमाल कर रहा है"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ऐप बैटरी का इस्तेमाल कर रहे हैं"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बैटरी और डेटा खर्च की जानकारी के लिए छूएं"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"इससे साथी ऐप्लिकेशन को बैकग्राउंड में फ़ोरग्राउंड सेवाएं चलाने की अनुमति मिलती है."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफ़ोन इस्तेमाल किया जा सकता है"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफ़ोन को ब्लॉक किया गया है"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिसप्ले का कॉन्टेंट नहीं दिखाया जा सकता"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"कोई दूसरा केबल इस्तेमाल करके फिर से कोशिश करें"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ऐसा हो सकता है कि केबल, डिसप्ले के साथ ठीक से काम न करे"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ऐसा हो सकता है कि यूएसबी-सी केबल, डिसप्ले के साथ ठीक से कनेक्ट न हो पाए"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen की सुविधा चालू है"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, कॉन्टेंट दिखाने के लिए दोनों स्क्रीन का इस्तेमाल कर रहा है"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 389a9562f5e8..81ac641a5e1e 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije troše bateriju"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povećavanje"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Upotreba pristupačnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> koristi bateriju"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Broj aplikacija koje koriste bateriju: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dodirnite da biste vidjeli pojedinosti o potrošnji baterije i podatkovnom prometu"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Popratnoj aplikaciji omogućuje da iz pozadine pokrene usluge u prednjem planu."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je dostupan"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Zrcaljenje na zaslon nije moguće"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Upotrijebite drugi kabel i pokušajte ponovno"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel možda ne podržava zaslone"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Vaš USB-C kabel možda nije ispravno povezan sa zaslonima"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dvostruki zaslon"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Uključen je dvostruki zaslon"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> upotrebljava oba zaslona za prikazivanje sadržaja"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 4bce83b917a2..9a389db091b7 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Akkumulátort használó alkalmazások"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Nagyítás"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Kisegítő lehetőségek használata"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Kijelző"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazás használja az akkumulátort"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> alkalmazás használja az akkumulátort"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Koppintson az akkumulátor- és adathasználat részleteinek megtekintéséhez"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lehetővé teszi a társalkalmazások számára, hogy előtérben futó szolgáltatásokat indítsanak a háttérből."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"A mikrofon rendelkezésre áll"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"A mikrofon le van tiltva"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nem lehet tükrözni a kijelzőre"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Használjon másik kábelt, és próbálja újra"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Előfordulhat, hogy a kábel nem támogatja a kijelzőket"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Előfordulhat, hogy az USB-C kábellel nem csatlakoztathatók megfelelően a kijelzők"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A Dual Screen funkció be van kapcsolva"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> mindkét kijelzőt használja a tartalmak megjelenítésére"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index e74ec542102d..ccea0cca5dfa 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Մարտկոցի լիցքը ծախսող հավելվածներ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Խոշորացում"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Հատուկ գործառույթների օգտագործում"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Էկրան"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"«<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածը ծախսում է մարտկոցի լիցքը"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> հավելված ծախսում է մարտկոցի լիցքը"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Հպեք՝ մարտկոցի և թրաֆիկի մանրամասները տեսնելու համար"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Թույլատրում է ուղեկցող հավելվածին ակտիվ ծառայություններ գործարկել ֆոնային ռեժիմից։"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Խոսափողը հասանելի է"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Խոսափողն արգելափակված է"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Չհաջողվեց հայելապատճենել էկրանին"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Օգտագործեք այլ մալուխ և նորից փորձեք"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Մալուխը կարող է համատեղելի չլինել էկրանների հետ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Հնարավոր է՝ USB-C մալուխը սխալ է միացված էկրանին"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen-ը միացված է"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն օգտագործում է երկու էկրանները"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index dc0993e4720f..47bbefde8700 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikasi yang menggunakan baterai"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan aksesibilitas"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Layar"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan baterai"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikasi sedang meggunakan baterai"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketuk untuk melihat detail penggunaan baterai dan data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Mengizinkan aplikasi pendamping memulai layanan latar depan dari latar belakang."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon diblokir"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat mencerminkan ke layar"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan coba lagi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak mendukung layar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C mungkin tidak terhubung dengan benar ke layar"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen aktif"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua layar untuk menampilkan konten"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 2af8969fe62f..f666308bb537 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Forrit sem nota rafhlöðuorku"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Stækkun"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Aðgengisnotkun"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skjár"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> notar rafhlöðuorku"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> forrit nota rafhlöðuorku"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ýttu til að fá upplýsingar um rafhlöðu- og gagnanotkun"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leyfir fylgiforriti að ræsa forgrunnsþjónustur úr bakgrunni."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Hljóðnemi er í boði"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Lokað er fyrir hljóðnemann"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekki er hægt að spegla á skjá"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Notaðu aðra snúru og reyndu aftur"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ekki er víst að snúran styðji skjái"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ekki er víst að USB-C-snúran tengist skjám á réttan hátt"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tveir skjáir"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Kveikt er á tveimur skjám"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> er að nota báða skjái til að sýna efni"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 7ea502cc94ef..e6bc55290ecb 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"App che consumano la batteria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ingrandimento"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilizzo dell\'accessibilità"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> sta consumando la batteria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> app stanno consumando la batteria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocca per conoscere i dettagli sull\'utilizzo dei dati e della batteria"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Consente a un\'app complementare di avviare servizi in primo piano dal background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfono disponibile"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfono bloccato"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Impossibile eseguire il mirroring al display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Usa un altro cavo e riprova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Il cavo potrebbe non supportare i display"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Il cavo USB-C potrebbe non collegarsi correttamente ai display"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Doppio schermo"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Doppio schermo attivo"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> sta usando entrambi i display per mostrare contenuti"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 22cbab2d04d1..53ec382af0f1 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"אפליקציות שמרוקנות את הסוללה"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"הגדלה"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"שימוש בנגישות"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"מסך"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בסוללה"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> אפליקציות משתמשות בסוללה"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"אפשר להקיש כדי לקבל פרטים על צריכה של נתונים וסוללה"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ההרשאה הזו מאפשרת לאפליקציה נלווית להפעיל מהרקע שירותים שפועלים בחזית."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"המיקרופון זמין"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"המיקרופון חסום"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"לא ניתן לשקף למסך"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"צריך להשתמש בכבל שונה ולנסות שוב"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"יכול להיות שהכבל לא תומך במסכים"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏יכול להיות שכבל ה-USB-C לא יתחבר למסכים כמו שצריך"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"מצב שני מסכים"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"מצב שני מסכים מופעל"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> משתמשת בשני המסכים כדי להציג תוכן"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 06b3445e80ee..961a8da87e4a 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"電池を消費しているアプリ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"拡大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ユーザー補助の使用"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ディスプレイ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」がバッテリーを使用しています"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個のアプリが電池を使用しています"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"タップしてバッテリーやデータの使用量を確認"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"バックグラウンドからのフォアグラウンド サービスの起動をコンパニオン アプリに許可します。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"マイクを利用できます"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"マイクがブロックされています"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ディスプレイにミラーリングできません"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"別のケーブルでもう一度お試しください"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ケーブルはディスプレイに対応していない可能性があります"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C ケーブルがディスプレイに正しく接続されていない可能性があります"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"デュアル スクリーン"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"デュアル スクリーン: ON"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>は 2 画面でコンテンツを表示しています"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index c8f6621f4e60..27f596bf7390 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ბატარეის მხარჯავი აპები"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"გადიდება"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"მარტივი წვდომის გამოყენება"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ეკრანი"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ბატარეას"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"ბატარეას <xliff:g id="NUMBER">%1$d</xliff:g> აპი იყენებს"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"შეეხეთ ბატარეისა და მონაცემების მოხმარების შესახებ დეტალური ინფორმაციისთვის"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"საშუალებას აძლევს კომპანიონ აპს, რომ გაუშვას უპირატესი სერვისები ფონური რეჟიმიდან."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"მიკროფონი ხელმისაწვდომია"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"მიკროფონი დაბლოკილია"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ეკრანზე არეკვლა შეუძლებელია"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"გამოიყენეთ სხვა კაბელი და ცადეთ ხელახლა"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"კაბელს შეიძლება არ ჰქონდეს ეკრანების მხარდაჭერა"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"თქვენი USB-C კაბელი შეიძლება სათანადოდ არ უკავშირდებოდეს ეკრანებს"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ორმაგი ეკრანი"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ორმაგი ეკრანი ჩართულია"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> იყენებს ორივე ეკრანს შინაარსის საჩვენებლად"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 1272c24a0215..1faea616398e 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Батареяны пайдаланып жатқан қолданбалар"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ұлғайту"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Арнайы мүмкіндіктерді қолдану"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батареяны пайдалануда"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> қолданба батареяны пайдалануда"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарея мен деректер трафигі туралы білу үшін түртіңіз"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Қосымша қолданбаға экрандық режимдегі қызметтерді фоннан іске қосуға рұқсат беріледі."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон қолжетімді."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон блокталған."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дисплейге көшірмені көрсету мүмкін емес"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Басқа кабельмен әрекетті қайталап көріңіз."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерді қолдамауы мүмкін"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабелі дисплейлерге дұрыс жалғанбаған болуы мүмкін."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen функциясы қосулы"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы контентті көрсету үшін екі дисплейді де пайдаланады."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 807761ef6dc5..470c39ed2ee6 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"កម្មវិធីដែល​កំពុងប្រើថ្ម"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ការពង្រីក"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ការប្រើប្រាស់​ភាពងាយស្រួល"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ផ្ទាំងអេក្រង់"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើថ្ម"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"កម្មវិធីចំនួន <xliff:g id="NUMBER">%1$d</xliff:g> កំពុងប្រើថ្ម"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ចុចដើម្បីមើលព័ត៌មានលម្អិតអំពីការប្រើប្រាស់ទិន្នន័យ និងថ្ម"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"អនុញ្ញាតឱ្យកម្មវិធីដៃគូចាប់ផ្តើមសេវាកម្មផ្ទៃខាងមុខពីផ្ទៃខាងក្រោយ។"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"អាចប្រើ​មីក្រូហ្វូនបាន"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"មីក្រូហ្វូនត្រូវ​បានទប់ស្កាត់"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"មិនអាចបញ្ចាំងទៅផ្ទាំងអេក្រង់បានទេ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ប្រើខ្សែផ្សេង រួចព្យាយាមម្តងទៀត"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ខ្សែប្រហែលជាមិនអាចប្រើជាមួយផ្ទាំងអេក្រង់បានទេ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ខ្សែ USB-C របស់អ្នក​ប្រហែលជា​មិនអាចភ្ជាប់​ផ្ទាំងអេក្រង់​បានត្រឹមត្រូវទេ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"អេក្រង់ពីរ"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"អេក្រង់ពីរត្រូវបានបើក"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> កំពុងប្រើផ្ទាំងអេក្រង់ទាំងពីរដើម្បីបង្ហាញខ្លឹមសារ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 742dfbbdd4f0..e2f24f69122e 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಬ್ಯಾಟರಿಯನ್ನು ಉಪಯೋಗಿಸುತ್ತಿವೆ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ಹಿಗ್ಗಿಸುವಿಕೆ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ಪ್ರವೇಶಿಸುವಿಕೆಯ ಬಳಕೆ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ಡಿಸ್‌ಪ್ಲೇ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್, ಬ್ಯಾಟರಿಯನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಬ್ಯಾಟರಿ ಬಳಸುತ್ತಿವೆ"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ಬ್ಯಾಟರಿ,ಡೇಟಾ ಬಳಕೆಯ ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಹಿನ್ನೆಲೆಯಿಂದ ಪ್ರಾರಂಭಿಸಲು ಕಂಪ್ಯಾನಿಯನ್ ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ಮೈಕ್ರೊಫೋನ್ ಲಭ್ಯವಿದೆ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ಮೈಕ್ರೊಫೋನ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ಡಿಸ್‌ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ಬೇರೆ ಕೇಬಲ್ ಬಳಸಿ ಹಾಗೂ ಪುನಃ ಪ್ರಯತ್ನಿಸಿ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ಡಿಸ್‌ಪ್ಲೇಗಳನ್ನು ಕೇಬಲ್ ಬೆಂಬಲಿಸದಿರಬಹುದು"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ನಿಮ್ಮ USB-C ಕೇಬಲ್ ಡಿಸ್‌ಪ್ಲೇಗಳಿಗೆ ಸರಿಯಾಗಿ ಕನೆಕ್ಟ್ ಆಗದಿರಬಹುದು"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ಆನ್ ಆಗಿದೆ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ಕಂಟೆಂಟ್‌ ಅನ್ನು ತೋರಿಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಎರಡೂ ಡಿಸ್‌ಪ್ಲೇಗಳನ್ನು ಬಳಸುತ್ತಿದೆ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index d2b6dc8663aa..0608283d5f3a 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"배터리를 소모하는 앱"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"확대"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"접근성 사용"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"디스플레이"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 배터리 사용 중"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"앱 <xliff:g id="NUMBER">%1$d</xliff:g>개에서 배터리 사용 중"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"탭하여 배터리 및 데이터 사용량 확인"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"호환 앱이 백그라운드에서 포그라운드 서비스를 시작할 수 있게 허용합니다."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"마이크 사용 가능"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"마이크가 차단됨"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"디스플레이에 미러링할 수 없음"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"다른 케이블을 사용하여 다시 시도해 보세요."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"디스플레이를 지원하지 않는 케이블일 수 있음"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"사용 중인 USB-C 케이블이 디스플레이에 제대로 연결되지 않을 수 있습니다."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen 켜짐"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 두 화면을 모두 사용하여 콘텐츠를 표시합니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 56c06967d345..853221ee31ef 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Колдонмолор батареяңызды коротууда"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Чоңойтуу"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Атайын мүмкүнчүлүктөрдүн колдонулушу"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосу батареяны пайдаланып жатат"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> колдонмо батареяны пайдаланып жатат"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батареянын кубаты жана трафиктин көлөмү жөнүндө билүү үчүн таптап коюңуз"</string>
@@ -1384,7 +1385,7 @@
<string name="test_harness_mode_notification_title" msgid="2282785860014142511">"Сыноо программасынын режими иштетилди"</string>
<string name="test_harness_mode_notification_message" msgid="3039123743127958420">"Сыноо программасынын режимин өчүрүү үчүн баштапкы параметрлерге кайтарыңыз."</string>
<string name="console_running_notification_title" msgid="6087888939261635904">"Сериялык консоль иштетилди"</string>
- <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Аны өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
+ <string name="console_running_notification_message" msgid="7892751888125174039">"Майнаптуулугуна таасири тиет. Өчүрүү үчүн операциялык тутумду жүктөгүчтү текшериңиз."</string>
<string name="mte_override_notification_title" msgid="4731115381962792944">"Cынамык MTE иштетилди"</string>
<string name="mte_override_notification_message" msgid="2441170442725738942">"Иштин майнаптуулугуна жана туруктуулугуна кедергиси тийиши мүмкүн. Өчүрүү үчүн түзмөктү өчүрүп-күйгүзүңүз. Эгер arm64.memtag.bootctl аркылуу иштетилген болсо, алдын ала \"none\" маанисин орнотуңуз."</string>
<string name="usb_contaminant_detected_title" msgid="4359048603069159678">"USB портунда суюктук же урандылар бар"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Көмөкчү колдонмого активдүү кызматтарды фондо иштетүүгө уруксат берет."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон жеткиликтүү"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон бөгөттөлгөн"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Экранга күзгүдөй чагылдыруу мүмкүн эмес"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайра аракет кылыңыз"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель дисплейлерди колдоого албашы мүмкүн"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабели дисплейлерге туура туташпашы мүмкүн"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Кош экран"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen күйүк"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контентти эки түзмөктө тең көрсөтүүдө"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index 8892f6047542..c743fc0b7b23 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ແອັບທີ່ກຳລັງໃຊ້ແບັດເຕີຣີ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ການຂະຫຍາຍ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ການໃຊ້ການຊ່ວຍເຂົ້າເຖິງ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ຈໍສະແດງຜົນ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ແອັບກຳລັງໃຊ້ແບັດເຕີຣີຢູ່"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ແຕະເພື່ອເບິ່ງລາຍລະອຽດການນຳໃຊ້ແບັດເຕີຣີ ແລະ ອິນເຕີເນັດ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ອະນຸຍາດຈາກເບື້ອງຫຼັງໃຫ້ແອັບຊ່ວຍເຫຼືອເລີ່ມໃຊ້ບໍລິການທີ່ເຮັດວຽກຢູ່ເບື້ອງໜ້າ."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ໄມໂຄຣໂຟນພ້ອມໃຫ້ນຳໃຊ້"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ໄມໂຄຣໂຟນຖືກບລັອກໄວ້"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ບໍ່ສາມາດສະທ້ອນໄປຫາຈໍສະແດງຜົນໄດ້"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ກະລຸນາໃຊ້ສາຍອື່ນແລ້ວລອງໃໝ່"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ສາຍອາດບໍ່ຮອງຮັບຈໍສະແດງຜົນ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ສາຍ USB-C ຂອງທ່ານອາດບໍ່ໄດ້ເຊື່ອມຕໍ່ກັບຈໍສະແດງຜົນຢ່າງຖືກຕ້ອງ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ໜ້າຈໍຄູ່"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ເປີດ Dual Screen ຢູ່"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ກຳລັງໃຊ້ຈໍສະແດງຜົນທັງສອງເພື່ອສະແດງເນື້ອຫາ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 8b4ff9793a15..ff665c5bbf8e 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programos, naudojančios akumuliatoriaus energiją"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Didinimas"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pritaikomumo funkcijų naudojimas"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekranas"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja akumuliatoriaus energiją"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programų, naudojančių akumuliatoriaus energiją: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Palieskite ir sužinokite išsamios informacijos apie akumuliatoriaus bei duomenų naudojimą"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Leidžiama papildomai programai paleisti priekinio plano paslaugas fone."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonas pasiekiamas"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonas užblokuotas"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Negalima bendrinti ekrano vaizdo ekrane"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Naudokite kitą laiką ir bandykite dar kartą"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Laidas gali nepalaikyti ekranų"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Gali būti, kad USB-C laidu nepavyksta tinkamai prisijungti prie ekranų"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Įjungta „Dual Screen“"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ naudoja abu ekranus turiniui rodyti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index c5093788ee20..4d369aaeb8d7 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Lietotnes, kas patērē akumulatora jaudu"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Palielinājums"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Pieejamības lietojums"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displejs"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Lietotne <xliff:g id="APP_NAME">%1$s</xliff:g> izmanto akumulatoru"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> lietotne(-es) izmanto akumulatoru"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pieskarieties, lai skatītu detalizētu informāciju par akumulatora un datu lietojumu"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ļauj palīglietotnei sākt priekšplāna pakalpojumus no fona."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofons ir pieejams."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofons ir bloķēts."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nevar spoguļot displeju"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Izmantojiet citu vadu un mēģiniet vēlreiz."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Iespējams, vads neatbalsta displejus"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Iespējams, jūsu USB-C vads nevarēs nodrošināt pareizu savienojumu ar displejiem."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen režīms"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ieslēgts Dual Screen režīms"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> satura rādīšanai izmanto abus displejus."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index 109e967acad4..29c9de8368d1 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликации што ја трошат батеријата"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Зголемување"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Користење на пристапноста"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерија"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апликации користат батерија"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Допрете за детали за батеријата и потрошениот интернет"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволува придружна апликација да започне услуги во преден план од заднината."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофонот е достапен"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофонот е блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не може да се отсликува за прикажување"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Користете друг кабел и обидете се повторно"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабелот можеби не поддржува екрани"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Кабелот USB-C можеби нема да се поврзе правилно со екраните"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Вклучен е Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ги користи двата екрани за да прикажува содржини"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 9ccf8b515985..46587ee13086 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"മാഗ്നിഫിക്കേഷൻ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ഉപയോഗസഹായി ഉപയോഗം"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ഡിസ്‌പ്ലേ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ആപ്പുകൾ ബാറ്ററി ഉപയോഗിക്കുന്നു"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ബാറ്ററി, ഡാറ്റ ഉപയോഗം എന്നിവയുടെ വിശദാംശങ്ങളറിയാൻ ടാപ്പുചെയ്യുക"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"പശ്ചാത്തലത്തിൽ നിന്ന് ഫോർഗ്രൗണ്ട് സേവനങ്ങൾ ആരംഭിക്കാൻ സഹകാരി ആപ്പിനെ അനുവദിക്കുന്നു."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"മൈക്രോഫോൺ ലഭ്യമാണ്"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"മൈക്രോഫോൺ ബ്ലോക്ക് ചെയ്‌തു"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ഡിസ്‌പ്ലേയിലേക്ക് മിറർ ചെയ്യാനാകില്ല"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"മറ്റൊരു കേബിൾ ഉപയോഗിച്ച് വീണ്ടും ശ്രമിക്കുക"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"കേബിൾ, ഡിസ്പ്ലേകളെ പിന്തുണച്ചേക്കില്ല"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"നിങ്ങളുടെ USB-C കേബിൾ, ഡിസ്‌പ്ലേകളിലേക്ക് ശരിയായി കണക്റ്റ് ആയേക്കില്ല"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"ഡ്യുവൽ സ്ക്രീൻ"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"ഡ്യുവൽ സ്ക്രീൻ ഓണാണ്"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ഉള്ളടക്കം കാണിക്കാൻ <xliff:g id="APP_NAME">%1$s</xliff:g> രണ്ട് ഡിസ്പ്ലേകളും ഉപയോഗിക്കുന്നു"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index a9ef358ad227..62a162e75540 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апп батарей ашиглаж байна"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Томруулах"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Хандалтын ашиглалт"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дэлгэц"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> батарей ашиглаж байна"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> апп батарей ашиглаж байна"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Батарей, дата ашиглалтын талаар дэлгэрэнгүйг харахын тулд товшино уу"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дэмжигч аппад нүүрэн талын үйлчилгээнүүдийг ардаас эхлүүлэхийг зөвшөөрнө."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофоныг ашиглах боломжгүй байна"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофоныг блоклосон байна"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Дэлгэцэд тусгал үүсгэх боломжгүй"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Өөр кабель ашиглаад, дахин оролдоно уу"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель нь дэлгэцүүдийг дэмждэггүй байж магадгүй"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Таны USB-C кабель дэлгэцүүдэд зохих ёсоор холбогдохгүй байж магадгүй"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen асаалттай байна"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> контент харуулахын тулд хоёр дэлгэцийг хоёуланг нь ашиглаж байна"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 485e9b614d01..87227eee7079 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"बॅटरी लवकर संपवणारी अ‍ॅप्स"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"मॅग्निफिकेशन"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"अ‍ॅक्सेसिबिलिटी वापर"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> बॅटरी वापरत आहे"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> अ‍ॅप्स बॅटरी वापरत आहेत"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"बॅटरी आणि डेटा वापराच्‍या तपशीलांसाठी टॅप करा"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"सहयोगी अ‍ॅपला बॅकग्राउंडमधून फोरग्राउंड सेवा सुरू करण्याची अनुमती देते."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"मायक्रोफोन उपलब्ध आहे"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"मायक्रोफोन ब्लॉक केलेला आहे"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेवर मिरर करू शकत नाही"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"वेगळी केबल वापरून पुन्हा प्रयत्न करा"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"केबल कदाचित डिस्प्लेना सपोर्ट करणार नाही"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तुमची USB-C केबल कदाचित डिस्प्लेना योग्यरीत्या कनेक्ट होणार नाही"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen सुरू आहे"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"आशय दाखवण्यासाठी <xliff:g id="APP_NAME">%1$s</xliff:g> दोन्ही डिस्प्ले वापरत आहे"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3d3fc7c83bd8..7794150ea86e 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apl yang menggunakan bateri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pembesaran"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Penggunaan kebolehaksesan"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Paparan"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> sedang menggunakan bateri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apl sedang menggunakan bateri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Ketik untuk mendapatkan butiran tentang penggunaan kuasa bateri dan data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Benarkan apl rakan memulakan perkhidmatan latar depan dari latar."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon tersedia"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon disekat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Tidak dapat menyegerakkan kepada paparan"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gunakan kabel lain dan cuba lagi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel mungkin tidak menyokong paparan"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C anda mungkin tidak bersambung kepada paparan dengan betul"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dwiskrin"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dwiskrin dihidupkan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> menggunakan kedua-dua paparan untuk menunjukkan kandungan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 39dd04316fc7..3aaaf8bd94bf 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"အက်ပ်များက ဘက်ထရီကုန်စေသည်"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ချဲ့ခြင်း"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"အများသုံးစွဲနိုင်မှုကို အသုံးပြုမှု"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ဖန်သားပြင်"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> က ဘက်ထရီကို အသုံးပြုနေသည်"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"အက်ပ် <xliff:g id="NUMBER">%1$d</xliff:g> ခုက ဘက်ထရီကို အသုံးပြုနေသည်"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ဘက်ထရီနှင့် ဒေတာအသုံးပြုမှု အသေးစိတ်ကို ကြည့်ရန် တို့ပါ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"နောက်ခံမှနေ၍ မျက်နှာစာဝန်ဆောင်မှုများ စတင်ရန် တွဲဖက် အက်ပ်ကို ခွင့်ပြုသည်။"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"မိုက်ခရိုဖုန်း သုံးနိုင်သည်"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"မိုက်ခရိုဖုန်း ပိတ်ထားသည်"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ဖန်သားပြင်တွင် စကရင်ပွား၍ မရပါ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"အခြားကေဘယ်ကြိုးသုံးပြီး ထပ်စမ်းကြည့်ပါ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ကေဘယ်ကြိုးက ဖန်သားပြင်များကို မပံ့ပိုးခြင်း ဖြစ်နိုင်သည်"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"သင့် USB-C ကေဘယ်ကြိုးသည် ဖန်သားပြင်များနှင့် မှန်ကန်စွာ ချိတ်ဆက်မထားခြင်း ဖြစ်နိုင်သည်"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen ဖွင့်ထားသည်"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် အကြောင်းအရာကို ပြရန် ဖန်သားပြင်နှစ်ခုစလုံးကို သုံးနေသည်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 5b1f77c9a068..9d6e8050de50 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apper bruker batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Forstørring"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Bruk av Tilgjengelighet"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skjerm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apper bruker batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trykk for detaljer om batteri- og databruk"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lar en følgeapp starte forgrunnstjenester fra bakgrunnen."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen er tilgjengelig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen er blokkert"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan ikke speile til skjermen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Bruk en annen kabel og prøv igjen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabelen støtter kanskje ikke skjermer"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C-kabelen din kobler seg kanskje ikke til skjermer på riktig måte"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen er på"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> bruker begge skjermene til å vise innhold"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 603fad34019a..3b7de10e0a91 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"एपहरूले ब्याट्री खपत गर्दै छन्"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"जुम इन गर्ने सुविधा"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"सर्वसुलभतासम्बन्धी सेवाहरूको प्रयोग"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"डिस्प्ले"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले ब्याट्री प्रयोग गर्दै छ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> एपहरूले ब्याट्री प्रयोग गर्दै छन्"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ब्याट्री र डेटाका प्रयोग सम्बन्धी विवरणहरूका लागि ट्याप गर्नुहोस्"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"यसले सहयोगी एपलाई ब्याकग्राउन्डमा फोरग्राउन्ड सेवाहरू चलाउने अनुमति दिन्छ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"माइक्रोफोन अनम्युट गरिएको छ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"माइक्रोफोन म्युट गरिएको छ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"डिस्प्लेमा मिरर गर्न सकिएन"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"अर्कै केबल प्रयोग गरी फेरि प्रयास गर्नुहोस्"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"यो केबल डिस्प्लेहरूमा प्रयोग गर्न नमिल्न सक्छ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"तपाईंको USB-C केबल डिस्प्लेहरूमा राम्रोसँग नजोडिन सक्छ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen अन छ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले सामग्री देखाउन दुई वटै डिस्प्ले प्रयोग गरिरहेको छ"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 50e261fd3bbc..ae7d366a1121 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps die de batterij gebruiken"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Vergroting"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Toegankelijkheidsgebruik"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Scherm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt de batterij"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps gebruiken de batterij"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tik voor batterij- en datagebruik"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hiermee kan een bijbehorende app services op de voorgrond vanuit de achtergrond starten."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfoon is beschikbaar"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfoon is geblokkeerd"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Kan niet spiegelen naar scherm"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gebruik een andere kabel en probeer het opnieuw"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"De kabel ondersteunt misschien geen schermen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Je USB-C-kabel sluit misschien niet goed aan op schermen"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen staat aan"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> gebruikt beide schermen om content te tonen"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 52c9bf2f8897..af76df84f3d0 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ଆପ୍‍ଗୁଡ଼ିକ ବ୍ୟାଟେରୀ ଖର୍ଚ୍ଚ କରିଥା\'ନ୍ତି"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ମେଗ୍ନିଫିକେସନ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ଆକ୍ସେସିବିଲିଟୀ ବ୍ୟବହାର"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ଡିସପ୍ଲେ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛି"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g>ଟି ଆପ୍‍ ବ୍ୟାଟେରୀ ବ୍ୟବହାର କରୁଛନ୍ତି"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ବ୍ୟାଟେରୀ ଏବଂ ଡାଟା ବ୍ୟବହାର ଉପରେ ବିବରଣୀ ପାଇଁ ଟାପ୍‍ କରନ୍ତୁ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ପୃଷ୍ଠପଟରୁ ଫୋରଗ୍ରାଉଣ୍ଡ ସେବାଗୁଡ଼ିକ ଆରମ୍ଭ କରିବାକୁ ଏକ ସହଯୋଗୀ ଆପକୁ ଅନୁମତି ଦିଏ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ମାଇକ୍ରୋଫୋନ ଉପଲବ୍ଧ ଅଛି"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ମାଇକ୍ରୋଫୋନକୁ ବ୍ଲକ କରାଯାଇଛି"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ଡିସପ୍ଲେ କରିବାକୁ ମିରର କରାଯାଇପାରିବ ନାହିଁ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ଏକ ଭିନ୍ନ କେବୁଲ ବ୍ୟବହାର କରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକୁ ସମର୍ଥନ କରିନପାରେ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ଆପଣଙ୍କ USB-C କେବୁଲ ଡିସପ୍ଲେଗୁଡ଼ିକ ସହ ସଠିକ ଭାବରେ କନେକ୍ଟ ହୋଇନପାରେ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ଚାଲୁ ଅଛି"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"ବିଷୟବସ୍ତୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g> ଉଭୟ ଡିସପ୍ଲେକୁ ବ୍ୟବହାର କରୁଛି"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 8034be88dc74..243e3e50022e 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ਬੈਟਰੀ ਦੀ ਖਪਤ ਕਰਨ ਵਾਲੀਆਂ ਐਪਾਂ"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"ਵੱਡਦਰਸ਼ੀਕਰਨ"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ਪਹੁੰਚਯੋਗਤਾ ਵਰਤੋਂ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ਡਿਸਪਲੇ"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਵੱਲੋਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ਐਪਾਂ ਬੈਟਰੀ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀਆਂ ਹਨ"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"ਬੈਟਰੀ ਅਤੇ ਡਾਟਾ ਵਰਤੋਂ ਸਬੰਧੀ ਵੇਰਵਿਆਂ ਲਈ ਟੈਪ ਕਰੋ"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ਸੰਬੰਧੀ ਐਪ ਨੂੰ ਬੈਕਗ੍ਰਾਊਂਡ ਤੋਂ ਫੋਰਗ੍ਰਾਊਂਡ ਸੇਵਾਵਾਂ ਸ਼ੁਰੂ ਕਰਨ ਦੀ ਆਗਿਆ ਮਿਲਦੀ ਹੈ।"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਉਪਲਬਧ ਹੈ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"ਕੋਈ ਵੱਖਰੀ ਕੇਬਲ ਵਰਤ ਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਦਾ ਸਮਰਥਨ ਨਾ ਕਰੇ"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਤੁਹਾਡੀ USB-C ਕੇਬਲ ਡਿਸਪਲੇਆਂ ਨਾਲ ਠੀਕ ਤਰ੍ਹਾਂ ਕਨੈਕਟ ਨਾ ਹੋਵੇ"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ਚਾਲੂ ਹੈ"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਸਮੱਗਰੀ ਨੂੰ ਦਿਖਾਉਣ ਲਈ ਦੋਵੇਂ ਡਿਸਪਲੇਆਂ ਦੀ ਵਰਤੋਂ ਕਰ ਰਹੀ ਹੈ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index b66ec02bcc8c..18bea3fee54d 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacje zużywające baterię"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Powiększenie"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Użycie ułatwień dostępu"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Wyświetlacz"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> zużywa baterię"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Liczba aplikacji zużywających baterię: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Kliknij, by wyświetlić szczegóły wykorzystania baterii i użycia danych"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Zezwala aplikacji towarzyszącej na uruchamianie usług działających na pierwszym planie, podczas gdy sama działa w tle."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon jest dostępny"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon jest zablokowany"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nie można utworzyć odbicia lustrzanego na wyświetlaczu"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Użyj innego kabla i spróbuj ponownie"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel może nie obsługiwać wyświetlaczy"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C może nie łączyć się prawidłowo z wyświetlaczami"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Podwójny ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Włączono podwójny ekran"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> korzysta z obu wyświetlaczy, aby pokazać treści"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 628627d53e8a..486318ddc02e 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index c83491e8d760..784f2a9ca4d0 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão a consumir bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Utilização da acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ecrã"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a consumir bateria."</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicações estão a consumir bateria."</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toque para obter detalhes acerca da utilização da bateria e dos dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que uma app associada em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar para o ecrã"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use um cabo diferente e tente novamente"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"O cabo pode não suportar ecrãs"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"O cabo USB-C pode não se ligar a ecrãs corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcionalidade Dual Screen ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a usar ambos os ecrãs para mostrar conteúdo"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 628627d53e8a..486318ddc02e 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão consumindo a bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ampliação"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uso de acessibilidade"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Tela"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está consumindo a bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> apps estão consumindo a bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tocar para ver detalhes sobre a bateria e o uso de dados"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite que um app complementar em segundo plano inicie serviços em primeiro plano."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"O microfone está disponível"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"O microfone está bloqueado"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Não é possível espelhar a tela"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Use outro cabo e tente de novo"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Talvez o cabo não tenha suporte a telas"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Seu cabo USB-C pode não se conectar a telas corretamente"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Tela dupla"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"A tela dupla está ativada"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está usando as duas telas para mostrar conteúdo"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 3389c6356c36..88f5044eefdf 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicațiile consumă bateria"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Mărire"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Folosirea accesibilității"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ecran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește bateria"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicații folosesc bateria"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Atinge pentru mai multe detalii privind bateria și utilizarea datelor"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permite unei aplicații partenere să inițieze servicii în prim-plan din fundal."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Microfonul este disponibil"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Microfonul este blocat"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nu se poate oglindi pe ecran"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Folosește alt cablu și încearcă din nou"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Cablul poate să nu fie compatibil cu ecranele"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Cablul USB-C poate să nu se conecteze corespunzător la ecrane"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Funcția Dual screen este activată"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> folosește ambele ecrane pentru a afișa conținut"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 920168abc73e..7f5e87f2bbb7 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Приложения, расходующие заряд"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увеличение"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Сервисы специальных возможностей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Экран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" расходует заряд"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Несколько приложений (<xliff:g id="NUMBER">%1$d</xliff:g>) расходуют заряд"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Нажмите, чтобы проверить энергопотребление и трафик"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Сопутствующее приложение сможет запускать активные службы из фонового режима."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон доступен."</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон заблокирован."</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Не удается дублировать на экран"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Используйте другой кабель или повторите попытку."</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель может не подходить для подключения к дисплеям"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Возможно, подключение дисплеев с помощью этого кабеля USB-C не поддерживается."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Функция Dual Screen включена"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> использует оба экрана."</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 5f743a44c75f..e90f1a2cea22 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"බැටරිය භාවිත කරන යෙදුම්"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"විශාලනය"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ප්‍රවේශ්‍යතා භාවිතය"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"සංදර්ශකය"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> බැටරිය භාවිත කරයි"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"යෙදුම් <xliff:g id="NUMBER">%1$d</xliff:g>ක් බැටරිය භාවිත කරයි"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"බැටරි හා දත්ත භාවිතය පිළිබඳව විස්තර සඳහා තට්ටු කරන්න"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"පසුබිමේ සිට පෙරබිම් සේවා ආරම්භ කිරීමට සහායක යෙදුමකට ඉඩ දෙයි."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"මයික්‍රෆෝනය තිබේ"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"මයික්‍රෆෝනය අවහිර කර ඇත"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"සංදර්ශකයට දර්පණය කළ නොහැක"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"වෙනස් කේබලයක් භාවිතා කර නැවත උත්සාහ කරන්න"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"කේබලය සංදර්ශක වෙත සහාය නොදැක්විය හැක"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"ඔබේ USB-C කේබලයට සංදර්ශකවලට නිසි ලෙස සම්බන්ධ නොවිය හැක"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen සක්‍රීයයි"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"අන්තර්ගතය පෙන්වීමට <xliff:g id="APP_NAME">%1$s</xliff:g> සංදර්ශන දෙකම භාවිත කරයි"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 6bbc11038200..3da576c4fd5a 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikácie spotrebúvajúce batériu"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zväčšenie"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Využitie dostupnosti"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Obrazovka"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> používa batériu"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Aplikácie (<xliff:g id="NUMBER">%1$d</xliff:g>) používajú batériu"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Klepnutím zobrazíte podrobnosti o batérii a spotrebe dát"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Umožňuje sprievodnej aplikácii spúšťať služby na popredí z pozadia."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofón je k dispozícii"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofón je blokovaný"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nedá sa zrkadliť do obrazovky"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Použite iný kábel a skúste znova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kábel nemusí podporovať obrazovky"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kábel USB‑C sa nemusí dať správne pripojiť k obrazovkám"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Je zapnutá funkcia Dual Screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> zobrazuje obsah na oboch obrazovkách"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 3999f9f30cfe..5b731b730398 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacije, ki porabljajo energijo baterije"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Povečava"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Uporaba funkcij za dostopnost"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Zaslon"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> porablja energijo baterije"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Toliko aplikacij porablja energijo baterije: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Dotaknite se za prikaz podrobnosti porabe baterije in prenosa podatkov"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Spremljevalni aplikaciji dovoljuje, da storitve v ospredju zažene iz ozadja."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon je na voljo"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon je blokiran"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ni mogoče zrcaliti zaslona"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Uporabite drug kabel in poskusite znova"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel morda ne podpira zaslonov"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kabel USB-C se morda ne more ustrezno povezati z zasloni."</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen je vklopljen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> uporablja oba zaslona za prikaz vsebine."</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index e7464633af5f..a4fd1bf18b39 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplikacionet që konsumojnë baterinë"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Zmadhimi"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Përdorimi i qasshmërisë"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekrani"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> po përdor baterinë"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplikacione po përdorin baterinë"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Trokit për detaje mbi baterinë dhe përdorimin e të dhënave"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Lejon një aplikacion shoqërues të fillojë shërbimet në plan të parë nga sfondi."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofoni ofrohet"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofoni është i bllokuar"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Nuk mund të pasqyrojë tek ekrani"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Përdor një kabllo tjetër dhe provo përsëri"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablloja nuk mund të mbështetë ekranet"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Kablloja jote USB-C mund të mos lidhet siç duhet me ekranet"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen është aktiv"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> po i përdor të dyja ekranet për të shfaqur përmbajtje"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index b5aefaafbec4..e5ae2f71a226 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -291,6 +291,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Апликације које троше батерију"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Увећање"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Коришћење Приступачности"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Екран"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи батерију"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Апликације (<xliff:g id="NUMBER">%1$d</xliff:g>) користе батерију"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Додирните за детаље о батерији и потрошњи података"</string>
@@ -314,7 +315,7 @@
<string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Музика и звук"</string>
<string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"приступ музици и аудио садржају на уређају"</string>
<string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Слике и видео снимци"</string>
- <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видео снимцима на уређају"</string>
+ <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видеима на уређају"</string>
<string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string>
<string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима звук"</string>
<string name="permgrouplab_activityRecognition" msgid="3324466667921775766">"Физичке активности"</string>
@@ -2334,6 +2335,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозвољава пратећој апликацији да покрене услуге у првом плану из позадине."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон је доступан"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон је блокиран"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Пресликавање на екран није могуће"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Употребите други кабл и пробајте поново"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабл не подржава екране"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C кабл се не повезује правилно са екранима"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen је укључен"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> користи оба екрана за приказивање садржаја"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index a24ca19a7b9f..f3917fe70d17 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Appar som drar batteri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Förstoring"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Tillgänglighetsanvändning"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skärm"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> drar batteri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> appar drar batteri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Tryck för information om batteri- och dataanvändning"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tillåter att en tillhörande app startar förgrundstjänster i bakgrunden."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofonen är tillgänglig"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofonen är blockerad"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Det går inte spegla till skärmen"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Använd en annan kabel och försök igen"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabeln kanske inte har stöd för skärmar"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Det kanske inte går att ansluta skärmar korrekt med den här USB-C-kabeln"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen är på"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> använder båda skärmarna för att visa innehåll"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7171486cdfb4..95c1b25e51f7 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Programu zinazotumia betri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukuzaji"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Matumizi ya zana za ufikivu"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Skrini"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia betri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Programu <xliff:g id="NUMBER">%1$d</xliff:g> zinatumia betri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Gusa ili upate maelezo kuhusu betri na matumizi ya data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Huruhusu programu oanifu kuanzisha huduma zinazoonekana kwenye skrini kutoka katika huduma zinazoendelea chinichini."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Maikrofoni inapatikana"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Maikrofoni imezuiwa"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Imeshindwa kuakisi kwenye skrini"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Tumia kebo tofauti kisha ujaribu tena"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Huenda kebo haioani na skrini"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Huenda kebo yako ya USB-C isiunganishwe vizuri na skrini"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual screen imewasha"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> inatumia skrini zote kuonyesha maudhui"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 4fe363e896b0..9ad4bd2bfb72 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"பேட்டரியைப் பயன்படுத்தும் ஆப்ஸ்"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"பெரிதாக்கல்"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"அணுகல்தன்மை உபயோகம்"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"டிஸ்ப்ளே"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகிறது"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ஆப்ஸ் பேட்டரியைப் பயன்படுத்துகின்றன"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"பேட்டரி மற்றும் டேட்டா உபயோக விவரங்களைக் காண, தட்டவும்"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"பின்னணியிலிருந்து முன்புலச் சேவைகளைத் தொடங்க துணைத் தயாரிப்பு ஆப்ஸை அனுமதிக்கும்."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"மைக்ரோஃபோன் இயக்கப்பட்டது"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"மைக்ரோஃபோன் முடக்கப்பட்டுள்ளது"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"டிஸ்ப்ளேயில் பிரதிபலிக்க முடியவில்லை"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"வெவ்வேறு கேபிள்களைப் பயன்படுத்தி மீண்டும் முயலவும்"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"டிஸ்ப்ளேக்களைக் கேபிள் ஆதரிக்காமல் இருக்கக்கூடும்"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"டிஸ்ப்ளேக்களில் உங்கள் USB-C கேபிள் சரியாக இணைக்கப்படாமல் இருக்கக்கூடும்"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"இரட்டைத் திரை"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"இரட்டைத் திரை அம்சம் இயக்கத்தில் உள்ளது"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"உள்ளடக்கத்தைக் காட்டுவதற்கு இரண்டு டிஸ்ப்ளேக்களையும் <xliff:g id="APP_NAME">%1$s</xliff:g> பயன்படுத்துகிறது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index aa22aeafe3b0..d1c336d863f7 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"బ్యాటరీని ఉపయోగిస్తున్న యాప్‌లు"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"మ్యాగ్నిఫికేషన్"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"యాక్సెసిబిలిటీ వినియోగం"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"డిస్‌ప్లే చేయండి"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> బ్యాటరీని ఉపయోగిస్తోంది"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> యాప్‌లు బ్యాటరీని ఉపయోగిస్తున్నాయి"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"బ్యాటరీ మరియు డేటా వినియోగ వివరాల కోసం నొక్కండి"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"బ్యాక్‌గ్రౌండ్ నుండి ఫోర్‌గ్రౌండ్ సర్వీస్‌లను ప్రారంభించడానికి సహాయక యాప్‌ను అనుమతిస్తుంది."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"మైక్రోఫోన్ అందుబాటులో ఉంది"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"మైక్రోఫోన్ బ్లాక్ చేయబడింది"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"డిస్‌ప్లే చేయడానికి మిర్రర్ చేయడం సాధ్యపడదు"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"వేరే కేబుల్‌ను ఉపయోగించి, మళ్లీ ట్రై చేయండి"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"డిస్‌ప్లేలను కేబుల్ సపోర్ట్ చేయకపోవచ్చు"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"మీ USB-C కేబుల్, డిస్‌ప్లేలకు సరిగ్గా కనెక్ట్ కాకపోవచ్చు"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen ఆన్‌లో ఉంది"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"కంటెంట్‌ను చూపడం కోసం <xliff:g id="APP_NAME">%1$s</xliff:g> రెండు డిస్‌ప్లేలనూ ఉపయోగిస్తోంది"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index eb4e2f71b085..4f53fcddae4a 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"แอปหลายแอปกำลังใช้แบตเตอรี่"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"การขยาย"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"การใช้งานการช่วยเหลือพิเศษ"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"จอแสดงผล"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้แบตเตอรี่"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"แอป <xliff:g id="NUMBER">%1$d</xliff:g> แอปกำลังใช้แบตเตอรี่"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"แตะเพื่อดูรายละเอียดเกี่ยวกับแบตเตอรี่และปริมาณการใช้อินเทอร์เน็ต"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"อนุญาตจากเบื้องหลังให้แอปที่ใช้ร่วมกันเริ่มการทำงานของบริการที่ทำงานอยู่เบื้องหน้า"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"ไมโครโฟนพร้อมใช้งาน"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"ไมโครโฟนถูกบล็อก"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"มิเรอร์ไปยังจอแสดงผลไม่ได้"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"โปรดใช้สายอื่นและลองอีกครั้ง"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"สายสัญญาณอาจไม่รองรับจอแสดงผล"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"สาย USB-C อาจเชื่อมต่อกับจอแสดงผลอย่างไม่ถูกต้อง"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen เปิดอยู่"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> กำลังใช้จอแสดงผลทั้งสองจอเพื่อแสดงเนื้อหา"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 7249c51ea9f5..2477698e740d 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Mga app na kumokonsumo ng baterya"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Pag-magnify"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Paggamit sa pagiging accessible"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Display"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Gumagamit ng baterya ang <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Gumagamit ng baterya ang <xliff:g id="NUMBER">%1$d</xliff:g> (na) app"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"I-tap para sa mga detalye tungkol sa paggamit ng baterya at data"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Nagbibigay-daan sa kasamang app na magsimula ng mga serbisyo sa foreground mula sa background."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Available ang mikropono"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Naka-block ang mikropono"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Hindi makapag-mirror sa display"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Gumamit ng ibang cable at subukan ulit"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Posibleng hindi sinusuportahan ng cable ang mga display"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Posibleng hindi kumonekta nang maayos sa mga display ang iyong USB-C cable"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Naka-on ang dual screen"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Ginagamit ng <xliff:g id="APP_NAME">%1$s</xliff:g> ang parehong display para magpakita ng content"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 8f02efda6968..2ebfe91be0cd 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Pil kullanan uygulamalar"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Büyütme"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Erişilebilirlik kullanımı"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Ekran"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> pil kullanıyor"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> uygulama pil kullanıyor"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Pil ve veri kullanımı ile ilgili ayrıntılar için dokunun"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Tamamlayıcı uygulamanın arka plandan ön plan hizmetlerini başlatmasına izin verir."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon kullanılabilir"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon engellenmiş"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ekrana yansıtılamıyor"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Farklı kablo kullanarak tekrar deneyin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kablo, ekranları desteklemeyebilir"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kablonuz ekranlara doğru şekilde bağlanamayabilir"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen açık"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>, içeriği göstermek için her iki ekranı da kullanıyor"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index d34e9769dcd3..d4cd2071bd87 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -292,6 +292,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Додатки, що використовують заряд акумулятора"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Збільшення"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Використання спеціальних можливостей"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Дисплей"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує заряд акумулятора"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"Додатків, що використовують заряд акумулятора: <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Торкніться, щоб перевірити використання акумулятора й трафік"</string>
@@ -2335,6 +2336,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Дозволяє супутньому додатку запускати активні сервіси у фоновому режимі."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Мікрофон доступний"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Мікрофон заблоковано"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Неможливо дублювати на дисплей"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Скористайтесь іншим кабелем і повторіть спробу"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Кабель може не підтримувати дисплеї"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ваш кабель USB-C може не підключатися до дисплеїв належним чином"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual Screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen увімкнено"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> використовує обидва екрани для показу контенту"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 631a573cfcf4..8634294d5a05 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"ایپس بیٹری خرچ کر رہی ہیں"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"میگنیفکیشن"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"ایکسیسبیلٹی کا استعمال"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"ڈسپلے"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> بیٹری کا استعمال کر رہی ہے"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ایپس بیٹری کا استعمال کر رہی ہیں"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"بیٹری اور ڈیٹا استعمال کے بارے میں تفصیلات کے لیے تھپتھپائیں"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"ساتھی ایپ کو پس منظر سے پیش منظر کی سروسز شروع کرنے کی اجازت دیتی ہے۔"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"مائیکروفون دستیاب ہے"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"مائیکروفون مسدود ہے"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"ڈسپلے پر دو طرفہ مطابقت پذیری ممکن نہیں ہے"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"مختلف کیبل استعمال کریں اور دوبارہ کوشش کریں"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"ہو سکتا ہے کہ کیبل ڈسپلیز کو سپورٹ نہ کرے"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"‏ہو سکتا ہے کہ آپ کی USB-C کیبل مناسب طریقے سے ڈسپلیز سے منسلک نہ ہو"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"دوہری اسکرین"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"دوہری اسکرین آن ہے"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"مواد دکھانے کیلئے <xliff:g id="APP_NAME">%1$s</xliff:g> دونوں ڈسپلیز استعمال کر رہی ہے"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 426234227813..fdea194637ae 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Batareya quvvatini sarflayotgan ilovalar"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Kattalashtirish"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Qulayliklar ishlatilishi"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Displey"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasi batareya quvvatini sarflamoqda"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ta ilova batareya quvvatini sarflamoqda"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Batareya va trafik sarfi tafsilotlari uchun ustiga bosing"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Hamroh ilovaga faol xizmatlarni fonda ishga tushirishga ruxsat beradi."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Mikrofon yoqildi"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Mikrofon bloklandi"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Displeyga translatsiya qilinmaydi"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Boshqa kabel yordamida qayta urining"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Kabel displeylar bilan ishlamasligi mumkin"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C kabelingiz displeylarga toʻgʻri ulanmasligi mumkin"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Ikkita ekran"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Ikki ekranli rejim yoniq"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> kontentni ikkala ekranda chiqarmoqda"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 78318b115010..12a61edfca58 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Các ứng dụng tiêu thụ pin"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Phóng to"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Việc sử dụng tính năng hỗ trợ tiếp cận"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Màn hình"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang sử dụng pin"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> ứng dụng đang sử dụng pin"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Nhấn để biết chi tiết về mức sử dụng dữ liệu và pin"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Cho phép một ứng dụng đồng hành bắt đầu các dịch vụ trên nền trước từ nền."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Micrô đang hoạt động"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Micrô đang bị chặn"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Không chiếu được nội dung lên màn hình"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Hãy dùng một cáp khác rồi thử lại"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Có thể cáp không hỗ trợ màn hình"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Có thể cáp USB-C của bạn chưa kết nối đúng cách với màn hình"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Dual screen"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Chế độ Dual screen bật"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> đang dùng cả hai màn hình để thể hiện nội dung"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 16c10138dfb6..d79a77221914 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"消耗电量的应用"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大功能"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"无障碍功能使用情况"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"显示屏"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在消耗电量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 个应用正在消耗电量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"点按即可详细了解电量和流量消耗情况"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允许配套应用从后台启动前台服务。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"麦克风可用"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"麦克风已被屏蔽"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"无法镜像到显示屏"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"请改用其他数据线并重试"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"数据线可能不支持显示屏"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"您的 USB-C 数据线可能没有正确连接到显示屏"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"双屏幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"双屏幕功能已开启"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g>正在使用双屏幕显示内容"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 06ec1dd42267..fe3644e68e5e 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"耗用電量的應用程式"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情況"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"顯示屏"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用電量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在使用電量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕按即可查看電池和數據用量詳情"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"可以使用麥克風"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"已封鎖麥克風"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他連接線,然後再試一次"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"連接線可能不支援顯示屏"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"你的 USB-C 連接線可能未妥善連接顯示屏"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"已開啟雙螢幕功能"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 81bdc37309f5..5b6520168d5d 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"正在耗用電量的應用程式"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"放大"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"無障礙功能使用情形"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"螢幕"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在耗用電量"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> 個應用程式正在耗用電量"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"輕觸即可查看電池和數據用量詳情"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"允許隨附應用程式從背景啟動前景服務。"</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"麥克風已可使用"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"麥克風已封鎖"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"無法將畫面鏡像投放至螢幕"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"請改用其他傳輸線,然後再試一次"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"傳輸線可能不支援螢幕"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"USB-C 傳輸線可能未妥善連接到螢幕"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"雙螢幕"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"雙螢幕功能已啟用"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」正在使用雙螢幕顯示內容"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index a2eb54526620..b701abe18d93 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -290,6 +290,7 @@
<string name="notification_channel_foreground_service" msgid="7102189948158885178">"Izinhlelo zokusebenza ezidla ibhethri"</string>
<string name="notification_channel_accessibility_magnification" msgid="1707913872219798098">"Ukukhuliswa"</string>
<string name="notification_channel_accessibility_security_policy" msgid="1727787021725251912">"Ukusetshenziswa kokufinyeleleka"</string>
+ <string name="notification_channel_display" msgid="6905032605735615090">"Bonisa"</string>
<string name="foreground_service_app_in_background" msgid="1439289699671273555">"<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa ibhethri"</string>
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> izinhlelo zokusebenza zisebenzisa ibhethri"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Thepha ngemininingwane ekusetshenzisweni kwebhethri nedatha"</string>
@@ -2333,6 +2334,10 @@
<string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Ivumela i-app ehambisanayo ukuthi iqale amasevisi angaphambili kusukela ngemuva."</string>
<string name="mic_access_on_toast" msgid="2666925317663845156">"Imakrofoni iyatholakala"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Imakrofoni ivinjiwe"</string>
+ <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Ayikwazi ukufanisa nesibonisi"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Sebenzisa ikhebuli ehlukile bese uyazama futhi"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_title" msgid="8477183783847392465">"Ikhebuli ingase ingasekeli iziboniso"</string>
+ <string name="connected_display_cable_dont_support_displays_notification_content" msgid="8080498819171483973">"Ikhebuli yakho ye-USB-C ingase ingaxhumi kahle kuzibonisi"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Isikrini esikabili"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Isikrini esikabili sivuliwe"</string>
<string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> isebenzisa zombili izibonisi ukukhombisa okuqukethwe"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 04fd70a96201..3496994fe173 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -10090,6 +10090,15 @@
<!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. -->
<attr name="lStar" format="float"/>
+ <!-- The attributes of the {@code <locale-config>} tag. -->
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <declare-styleable name="LocaleConfig">
+ <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
+ the strings in values/strings.xml (the default strings in the directory with no locale
+ qualifier) are in. -->
+ <attr name="defaultLocale" format="string"/>
+ </declare-styleable>
+
<!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. -->
<declare-styleable name="LocaleConfig_Locale">
<!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index adc7fe0922aa..4cd4f638e191 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -110,6 +110,8 @@
<eat-comment/>
<staging-public-group type="attr" first-id="0x01bd0000">
+ <!-- @FlaggedApi("android.content.res.default_locale") -->
+ <public name="defaultLocale"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 445ddf52bf1c..2993a0e63228 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -65,7 +65,6 @@ android_test {
"device-time-shell-utils",
"testables",
"com.android.text.flags-aconfig-java",
- "flag-junit",
],
libs: [
@@ -76,7 +75,6 @@ android_test {
"framework",
"ext",
"framework-res",
- "android.view.flags-aconfig-java",
],
jni_libs: [
"libpowermanagertest_jni",
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 7f3e01432dd9..9430ba6a939a 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -857,7 +857,8 @@ public class NotificationTest {
assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor());
assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor());
assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor());
- assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor());
+ assertEquals(cDay.getOnTertiaryAccentTextColor(),
+ cNight.getOnTertiaryAccentTextColor());
assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
@@ -1830,7 +1831,7 @@ public class NotificationTest {
assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
- assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getOnTertiaryAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getRippleAlpha()).isAtLeast(0x00);
@@ -1848,7 +1849,7 @@ public class NotificationTest {
assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
// The text that is used within the accent color DOES need to have contrast
- assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
+ assertContrastIsAtLeast(c.getOnTertiaryAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
}
private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor,
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
index 531404bffd50..d10cf1691408 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -62,4 +62,24 @@ public class ClientTransactionTests {
verify(callback2, times(1)).preExecute(clientTransactionHandler);
verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
}
+
+ @Test
+ public void testPreExecuteTransactionItems() {
+ final ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ final ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ final ClientTransactionHandler clientTransactionHandler =
+ mock(ClientTransactionHandler.class);
+
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(stateRequest);
+
+ transaction.preExecute(clientTransactionHandler);
+
+ verify(callback1, times(1)).preExecute(clientTransactionHandler);
+ verify(callback2, times(1)).preExecute(clientTransactionHandler);
+ verify(stateRequest, times(1)).preExecute(clientTransactionHandler);
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 44a4d580dbc0..f2b0f2e622b8 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -247,6 +247,31 @@ public class TransactionExecutorTests {
}
@Test
+ public void testExecuteTransactionItems_transactionResolution() {
+ ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ when(callback1.getPostExecutionState()).thenReturn(UNDEFINED);
+ ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
+ ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ IBinder token = mock(IBinder.class);
+ when(stateRequest.getActivityToken()).thenReturn(token);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+ ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(stateRequest);
+
+ transaction.preExecute(mTransactionHandler);
+ mExecutor.execute(transaction);
+
+ InOrder inOrder = inOrder(mTransactionHandler, callback1, callback2, stateRequest);
+ inOrder.verify(callback1).execute(eq(mTransactionHandler), any());
+ inOrder.verify(callback2).execute(eq(mTransactionHandler), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ }
+
+ @Test
public void testDoNotLaunchDestroyedActivity() {
final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
@@ -279,12 +304,43 @@ public class TransactionExecutorTests {
}
@Test
+ public void testExecuteTransactionItems_doNotLaunchDestroyedActivity() {
+ final Map<IBinder, DestroyActivityItem> activitiesToBeDestroyed = new ArrayMap<>();
+ when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+ // Assume launch transaction is still in queue, so there is no client record.
+ when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+ // An incoming destroy transaction enters binder thread (preExecute).
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
+ destroyTransaction.addTransactionItem(
+ DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+ destroyTransaction.preExecute(mTransactionHandler);
+ // The activity should be added to to-be-destroyed container.
+ assertEquals(1, activitiesToBeDestroyed.size());
+
+ // A previous queued launch transaction runs on main thread (execute).
+ final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */);
+ final LaunchActivityItem launchItem =
+ spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
+ launchTransaction.addTransactionItem(launchItem);
+ mExecutor.execute(launchTransaction);
+
+ // The launch transaction should not be executed because its token is in the
+ // to-be-destroyed container.
+ verify(launchItem, never()).execute(any(), any());
+
+ // After the destroy transaction has been executed, the token should be removed.
+ mExecutor.execute(destroyTransaction);
+ assertTrue(activitiesToBeDestroyed.isEmpty());
+ }
+
+ @Test
public void testActivityResultRequiredStateResolution() {
when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
- IBinder token = mock(IBinder.class);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addCallback(postExecItem);
@@ -300,6 +356,26 @@ public class TransactionExecutorTests {
}
@Test
+ public void testExecuteTransactionItems_activityResultRequiredStateResolution() {
+ when(mTransactionHandler.getActivity(any())).thenReturn(mock(Activity.class));
+
+ PostExecItem postExecItem = new PostExecItem(ON_RESUME);
+
+ ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(postExecItem);
+
+ // Verify resolution that should get to onPause
+ mClientRecord.setState(ON_RESUME);
+ mExecutor.executeTransactionItems(transaction);
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_PAUSE), eq(transaction));
+
+ // Verify resolution that should get to onStart
+ mClientRecord.setState(ON_STOP);
+ mExecutor.executeTransactionItems(transaction);
+ verify(mExecutor).cycleToPath(eq(mClientRecord), eq(ON_START), eq(transaction));
+ }
+
+ @Test
public void testClosestStateResolutionForSameState() {
final int[] allStates = new int[] {
ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY};
@@ -444,6 +520,18 @@ public class TransactionExecutorTests {
mExecutor.executeCallbacks(transaction);
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testExecuteTransactionItems_activityItemNullRecordThrowsException() {
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(activityItem);
+ when(mTransactionHandler.getActivityClient(token)).thenReturn(null);
+
+ mExecutor.executeTransactionItems(transaction);
+ }
+
@Test
public void testActivityItemExecute() {
final IBinder token = mock(IBinder.class);
@@ -464,6 +552,26 @@ public class TransactionExecutorTests {
inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
}
+ @Test
+ public void testExecuteTransactionItems_activityItemExecute() {
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
+ when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+ when(activityItem.getActivityToken()).thenReturn(token);
+ transaction.addTransactionItem(activityItem);
+ final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ transaction.addTransactionItem(stateRequest);
+ when(stateRequest.getActivityToken()).thenReturn(token);
+ when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
+
+ mExecutor.execute(transaction);
+
+ final InOrder inOrder = inOrder(activityItem, stateRequest);
+ inOrder.verify(activityItem).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ inOrder.verify(stateRequest).execute(eq(mTransactionHandler), eq(mClientRecord), any());
+ }
+
private static int[] shuffledArray(int[] inputArray) {
final List<Integer> list = Arrays.stream(inputArray).boxed().collect(Collectors.toList());
Collections.shuffle(list);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 7d047c93520f..4aa62c503a41 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -285,6 +285,10 @@ public class TransactionParcelTests {
78 /* configChanges */);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
+ transaction.addTransactionItem(callback1);
+ transaction.addTransactionItem(callback2);
+ transaction.addTransactionItem(lifecycleRequest);
+
transaction.addCallback(callback1);
transaction.addCallback(callback2);
transaction.setLifecycleStateRequest(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3f78396e3a70..0d687b24a4e5 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -16,7 +16,7 @@
package android.graphics;
-import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK;
+import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -931,7 +931,7 @@ public class TypefaceSystemFallbackTest {
return String.format(xml, op, lang, font);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_prepend() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -947,7 +947,7 @@ public class TypefaceSystemFallbackTest {
assertB3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_replace() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -963,7 +963,7 @@ public class TypefaceSystemFallbackTest {
assertB3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_append() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -979,7 +979,7 @@ public class TypefaceSystemFallbackTest {
assertA3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_ScriptMismatch() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
@@ -995,7 +995,7 @@ public class TypefaceSystemFallbackTest {
assertA3emFontIsUsed(typeface);
}
- @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK)
@Test
public void testBuildSystemFallback__Customization_locale_SubscriptMatch() {
final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java
index 1f00a7a12c54..88fc8267f5b2 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@ public class LocaleListTest extends TestCase {
// restore the original values
LocaleList.setDefault(originalLocaleList, originalLocaleIndex);
}
+
+ @SmallTest
+ public void testIntersection() {
+ LocaleList localesWithN = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.ITALIAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.JAPAN,
+ Locale.CANADA,
+ Locale.CANADA_FRENCH);
+ LocaleList localesWithE = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.CANADA_FRENCH);
+ LocaleList localesWithNAndE = new LocaleList(
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.CHINESE,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ Locale.FRANCE,
+ Locale.GERMANY,
+ Locale.CANADA_FRENCH);
+
+ assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN)));
+ }
}
diff --git a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
index 517aeae53784..0855268411eb 100644
--- a/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
+++ b/core/tests/coretests/src/android/service/notification/NotificationRankingUpdateTest.java
@@ -20,8 +20,6 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -42,16 +40,12 @@ import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.Parcel;
import android.os.SharedMemory;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Flag;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.FlagResolver;
-
-import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -71,6 +65,9 @@ public class NotificationRankingUpdateTest {
private NotificationChannel mNotificationChannel;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
// TODO(b/284297289): remove this flag set once resolved.
@Parameterized.Parameters(name = "rankingUpdateAshmem={0}")
public static Boolean[] getRankingUpdateAshmem() {
@@ -424,30 +421,11 @@ public class NotificationRankingUpdateTest {
mNotificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "test channel",
NotificationManager.IMPORTANCE_DEFAULT);
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = new FlagResolver() {
- @Override
- public boolean isEnabled(Flag flag) {
- if (flag.mSysPropKey.equals(RANKING_UPDATE_ASHMEM.mSysPropKey)) {
- return mRankingUpdateAshmem;
- }
- return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
- }
-
- @Override
- public int getIntValue(Flag flag) {
- return 0;
- }
-
- @Override
- public String getStringValue(Flag flag) {
- return null;
- }
- };
- }
-
- @After
- public void tearDown() {
- SystemUiSystemPropertiesFlags.TEST_RESOLVER = null;
+ if (mRankingUpdateAshmem) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+ } else {
+ mSetFlagsRule.disableFlags(Flags.FLAG_RANKING_UPDATE_ASHMEM);
+ }
}
/**
@@ -497,8 +475,7 @@ public class NotificationRankingUpdateTest {
parcel.setDataPosition(0);
NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
// The rankingUpdate file descriptor is only non-null in the new path.
- if (SystemUiSystemPropertiesFlags.getResolver().isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.RANKING_UPDATE_ASHMEM)) {
+ if (Flags.rankingUpdateAshmem()) {
assertTrue(nru1.isFdNotNullAndClosed());
}
detailedAssertEquals(nru, nru1);
@@ -636,7 +613,7 @@ public class NotificationRankingUpdateTest {
@Test
public void testRankingUpdate_writesSmartActionToParcel() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -674,7 +651,7 @@ public class NotificationRankingUpdateTest {
@Test
public void testRankingUpdate_handlesEmptySmartActionList() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
ArrayList<Notification.Action> actions = new ArrayList<>();
@@ -697,7 +674,7 @@ public class NotificationRankingUpdateTest {
@Test
public void testRankingUpdate_handlesNullSmartActionList() {
- if (!mRankingUpdateAshmem) {
+ if (!Flags.rankingUpdateAshmem()) {
return;
}
NotificationListenerService.Ranking ranking =
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index 2ca99943a8a0..23668a4b2afb 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -20,4 +20,7 @@ per-file *ContentRecord* = file:/services/core/java/com/android/server/wm/OWNER
per-file *ScrollCapture*.java = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
# Stylus
-per-file stylus/* = file:/core/java/android/text/OWNERS \ No newline at end of file
+per-file stylus/* = file:/core/java/android/text/OWNERS
+
+# View
+file:/core/java/android/view/OWNERS \ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
new file mode 100644
index 000000000000..6acab36f5f9c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScrollFeedbackProviderTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class ScrollFeedbackProviderTest {
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ @Test
+ public void testDefaultProviderType() {
+ View view = new View(mContext);
+
+ ScrollFeedbackProvider provider = ScrollFeedbackProvider.createProvider(view);
+
+ assertThat(provider).isInstanceOf(HapticScrollFeedbackProvider.class);
+ }
+
+ @Test
+ public void testDefaultProvider_createsDistinctProvidesOnMultipleCalls() {
+ View view1 = new View(mContext);
+ View view2 = new View(mContext);
+
+ ScrollFeedbackProvider view1Provider1 = ScrollFeedbackProvider.createProvider(view1);
+ ScrollFeedbackProvider view1Provider2 = ScrollFeedbackProvider.createProvider(view1);
+ ScrollFeedbackProvider view2Provider = ScrollFeedbackProvider.createProvider(view2);
+
+ assertThat(view1Provider1 == view1Provider2).isFalse();
+ assertThat(view1Provider1 == view2Provider).isFalse();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
index e117051ba9de..71bdce4ecb0e 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlRegistryTests.java
@@ -23,6 +23,7 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.eq;
@@ -148,6 +149,28 @@ public class SurfaceControlRegistryTests {
reporter.assertLastReportedSetEquals(sc5, sc6, sc7, sc8);
}
+ @Test
+ public void testCallStackDebugging_matchesFilters() {
+ SurfaceControlRegistry registry = SurfaceControlRegistry.getProcessInstance();
+
+ // Specific name, any call
+ registry.setCallStackDebuggingParams("com.android.app1", "");
+ assertFalse(registry.matchesForCallStackDebugging("com.android.noMatchApp", "setAlpha"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+
+ // Any name, specific call
+ registry.setCallStackDebuggingParams("", "setAlpha");
+ assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+
+ // Specific name, specific call
+ registry.setCallStackDebuggingParams("com.android.app1", "setAlpha");
+ assertFalse(registry.matchesForCallStackDebugging("com.android.app1", "setLayer"));
+ assertFalse(registry.matchesForCallStackDebugging("com.android.app2", "setAlpha"));
+ assertTrue(registry.matchesForCallStackDebugging("com.android.app1", "setAlpha"));
+ }
+
private SurfaceControl buildTestSurface() {
return new SurfaceControl.Builder()
.setContainerLayer()
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 1a38decae604..6a9fc04230f8 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,11 +17,6 @@
package android.view;
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
-import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
-import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -53,12 +48,8 @@ import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.SystemProperties;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
@@ -106,10 +97,6 @@ public class ViewRootImplTest {
// state after the test completes.
private static boolean sOriginalTouchMode;
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@BeforeClass
public static void setUpClass() {
sContext = sInstrumentation.getTargetContext();
@@ -440,129 +427,6 @@ public class ViewRootImplTest {
assertThat(result).isFalse();
}
- /**
- * Test the default values are properly set
- */
- @UiThreadTest
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_getDefaultValues() {
- ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
- sContext.getDisplayNoVerify());
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
- }
-
- /**
- * Test the value of the frame rate cateogry based on the visibility of a view
- * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
- * Visible: FRAME_RATE_CATEGORY_NORMAL
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
- View view = new View(sContext);
- attachViewToWindow(view);
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- sInstrumentation.runOnMainSync(() -> {
- view.setVisibility(View.INVISIBLE);
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- });
-
- sInstrumentation.runOnMainSync(() -> {
- view.setVisibility(View.VISIBLE);
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NORMAL);
- });
- }
-
- /**
- * Test the value of the frame rate cateogry based on the size of a view.
- * The current threshold value is 7% of the screen size
- * <7%: FRAME_RATE_CATEGORY_LOW
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
- View view = new View(sContext);
- WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
- wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
- wmlp.width = 1;
- wmlp.height = 1;
-
- sInstrumentation.runOnMainSync(() -> {
- WindowManager wm = sContext.getSystemService(WindowManager.class);
- wm.addView(view, wmlp);
- });
- sInstrumentation.waitForIdleSync();
-
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- sInstrumentation.runOnMainSync(() -> {
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
- });
- }
-
- /**
- * Test the value of the frame rate cateogry based on the size of a view.
- * The current threshold value is 7% of the screen size
- * >=7% : FRAME_RATE_CATEGORY_NORMAL
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
- View view = new View(sContext);
- WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
- wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
-
- sInstrumentation.runOnMainSync(() -> {
- WindowManager wm = sContext.getSystemService(WindowManager.class);
- Display display = wm.getDefaultDisplay();
- DisplayMetrics metrics = new DisplayMetrics();
- display.getMetrics(metrics);
- wmlp.width = (int) (metrics.widthPixels * 0.9);
- wmlp.height = (int) (metrics.heightPixels * 0.9);
- wm.addView(view, wmlp);
- });
- sInstrumentation.waitForIdleSync();
-
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- sInstrumentation.runOnMainSync(() -> {
- view.invalidate();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- });
- }
-
- /**
- * Test how values of the frame rate cateogry are aggregated.
- * It should take the max value among all of the voted categories per frame.
- */
- @Test
- @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
- public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
- View view = new View(sContext);
- attachViewToWindow(view);
- sInstrumentation.runOnMainSync(() -> {
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
- FRAME_RATE_CATEGORY_NO_PREFERENCE);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
- assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
- });
- }
-
@Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b71aaf3fc2e6..cc73ecee2146 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -2887,6 +2887,12 @@
"group": "WM_DEBUG_RESIZE",
"at": "com\/android\/server\/wm\/WindowState.java"
},
+ "419378610": {
+ "message": "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"422634333": {
"message": "First draw done in potential wallpaper target %s",
"level": "VERBOSE",
@@ -4339,12 +4345,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "1936800105": {
- "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"1945495497": {
"message": "Focused window didn't have a valid surface drawn.",
"level": "DEBUG",
diff --git a/graphics/java/android/framework_graphics.aconfig b/graphics/java/android/framework_graphics.aconfig
index e030dad6bf14..9a0a22a08a0c 100644
--- a/graphics/java/android/framework_graphics.aconfig
+++ b/graphics/java/android/framework_graphics.aconfig
@@ -2,7 +2,7 @@ package: "com.android.graphics.flags"
flag {
name: "exact_compute_bounds"
- namespace: "framework_graphics"
+ namespace: "core_graphics"
description: "Add a function without unused exact param for computeBounds."
bug: "304478551"
} \ No newline at end of file
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 9fde0fd6e6ab..4eaa01309ab1 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2113,6 +2113,31 @@ public class Paint {
* The recommended additional space to add between lines of text.
*/
public float leading;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof FontMetrics)) return false;
+ FontMetrics that = (FontMetrics) o;
+ return that.top == top && that.ascent == ascent && that.descent == descent
+ && that.bottom == bottom && that.leading == leading;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(top, ascent, descent, bottom, leading);
+ }
+
+ @Override
+ public String toString() {
+ return "FontMetrics{"
+ + "top=" + top
+ + ", ascent=" + ascent
+ + ", descent=" + descent
+ + ", bottom=" + bottom
+ + ", leading=" + leading
+ + '}';
+ }
}
/**
@@ -2309,6 +2334,33 @@ public class Paint {
*/
public int leading;
+ /**
+ * Set values from {@link FontMetricsInt}.
+ * @param fontMetricsInt a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetricsInt fontMetricsInt) {
+ top = fontMetricsInt.top;
+ ascent = fontMetricsInt.ascent;
+ descent = fontMetricsInt.descent;
+ bottom = fontMetricsInt.bottom;
+ leading = fontMetricsInt.leading;
+ }
+
+ /**
+ * Set values from {@link FontMetrics} with rounding accordingly.
+ * @param fontMetrics a font metrics.
+ */
+ @FlaggedApi(FLAG_FIX_LINE_HEIGHT_FOR_LOCALE)
+ public void set(@NonNull FontMetrics fontMetrics) {
+ // See GraphicsJNI::set_metrics_int method for consistency.
+ top = (int) Math.floor(fontMetrics.top);
+ ascent = Math.round(fontMetrics.ascent);
+ descent = Math.round(fontMetrics.descent);
+ bottom = (int) Math.ceil(fontMetrics.bottom);
+ leading = Math.round(fontMetrics.leading);
+ }
+
@Override public String toString() {
return "FontMetricsInt: top=" + top + " ascent=" + ascent +
" descent=" + descent + " bottom=" + bottom +
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index 6e04a2f5e405..ba5628cd2bc1 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -182,7 +182,7 @@ public class FontCustomizationParser {
// For ignoring the customization, consume the new-locale-family element but don't
// register any customizations.
- if (com.android.text.flags.Flags.customLocaleFallback()) {
+ if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) {
outCustomization.add(new FontConfig.Customization.LocaleFallback(
Locale.forLanguageTag(lang), intOp, family));
}
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index 4c753565eb5b..685fd825d43e 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -16,7 +16,7 @@
package android.graphics.fonts;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -145,7 +145,7 @@ public final class FontFamily {
* @return A variable font family. null if a variable font cannot be built from the given
* fonts.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public @Nullable FontFamily buildVariableFamily() {
int variableFamilyType = analyzeAndResolveVariableType(mFonts);
if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) {
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 618aa5b5019c..3ef714ed8bdf 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -241,7 +241,7 @@ public final class SystemFonts {
int configVersion
) {
final String fontsXml;
- if (com.android.text.flags.Flags.deprecateFontsXml()) {
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
@@ -272,7 +272,7 @@ public final class SystemFonts {
*/
public static @NonNull FontConfig getSystemPreinstalledFontConfig() {
final String fontsXml;
- if (com.android.text.flags.Flags.deprecateFontsXml()) {
+ if (com.android.text.flags.Flags.newFontsFallbackXml()) {
fontsXml = FONTS_XML;
} else {
fontsXml = LEGACY_FONTS_XML;
diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java
index 569f9b6fe945..7932e3334063 100644
--- a/graphics/java/android/graphics/text/PositionedGlyphs.java
+++ b/graphics/java/android/graphics/text/PositionedGlyphs.java
@@ -16,7 +16,7 @@
package android.graphics.text;
-import static com.android.text.flags.Flags.FLAG_DEPRECATE_FONTS_XML;
+import static com.android.text.flags.Flags.FLAG_NEW_FONTS_FALLBACK_XML;
import android.annotation.FlaggedApi;
import android.annotation.IntRange;
@@ -173,7 +173,7 @@ public final class PositionedGlyphs {
* @param index the glyph index
* @return true if the fake bold option is on, otherwise off.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public boolean getFakeBold(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetFakeBold(mLayoutPtr, index);
@@ -185,7 +185,7 @@ public final class PositionedGlyphs {
* @param index the glyph index
* @return true if the fake italic option is on, otherwise off.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public boolean getFakeItalic(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
return nGetFakeItalic(mLayoutPtr, index);
@@ -195,7 +195,7 @@ public final class PositionedGlyphs {
* A special value returned by {@link #getWeightOverride(int)} and
* {@link #getItalicOverride(int)} that indicates no font variation setting is overridden.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public static final float NO_OVERRIDE = Float.MIN_VALUE;
/**
@@ -205,7 +205,7 @@ public final class PositionedGlyphs {
* @param index the glyph index
* @return overridden weight value or {@link #NO_OVERRIDE}.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public float getWeightOverride(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
float value = nGetWeightOverride(mLayoutPtr, index);
@@ -223,7 +223,7 @@ public final class PositionedGlyphs {
* @param index the glyph index
* @return overridden weight value or {@link #NO_OVERRIDE}.
*/
- @FlaggedApi(FLAG_DEPRECATE_FONTS_XML)
+ @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML)
public float getItalicOverride(@IntRange(from = 0) int index) {
Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index");
float value = nGetItalicOverride(mLayoutPtr, index);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
new file mode 100644
index 000000000000..ff49cdcab349
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * The parameter to create an overlay container that retrieved from
+ * {@link android.app.ActivityOptions} bundle.
+ */
+class OverlayCreateParams {
+
+ // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS =
+ "androidx.window.extensions.OverlayCreateParams";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
+ "androidx.window.extensions.OverlayCreateParams.taskId";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
+ "androidx.window.extensions.OverlayCreateParams.tag";
+
+ @VisibleForTesting
+ static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
+ "androidx.window.extensions.OverlayCreateParams.bounds";
+
+ private final int mTaskId;
+
+ @NonNull
+ private final String mTag;
+
+ @NonNull
+ private final Rect mBounds;
+
+ OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
+ mTaskId = taskId;
+ mTag = requireNonNull(tag);
+ mBounds = requireNonNull(bounds);
+ }
+
+ int getTaskId() {
+ return mTaskId;
+ }
+
+ @NonNull
+ String getTag() {
+ return mTag;
+ }
+
+ @NonNull
+ Rect getBounds() {
+ return mBounds;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mTaskId;
+ result = 31 * result + mTag.hashCode();
+ result = 31 * result + mBounds.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) return true;
+ if (!(obj instanceof OverlayCreateParams thatParams)) return false;
+ return mTaskId == thatParams.mTaskId
+ && mTag.equals(thatParams.mTag)
+ && mBounds.equals(thatParams.mBounds);
+ }
+
+ @Override
+ public String toString() {
+ return OverlayCreateParams.class.getSimpleName() + ": {"
+ + "taskId=" + mTaskId
+ + ", tag=" + mTag
+ + ", bounds=" + mBounds
+ + "}";
+ }
+
+ /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
+ @Nullable
+ static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
+ final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
+ if (paramsBundle == null) {
+ return null;
+ }
+ final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
+ final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
+ final Rect bounds = requireNonNull(paramsBundle.getParcelable(
+ KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
+
+ return new OverlayCreateParams(taskId, tag, bounds);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index cdfc4c87d271..49606f0bf485 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,9 +40,10 @@ import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceh
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -87,6 +88,7 @@ import androidx.window.extensions.embedding.TransactionManager.TransactionRecord
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
@@ -123,8 +125,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* and unregistered via {@link #clearSplitAttributesCalculator()}.
* This is called when:
* <ul>
- * <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
- * WindowContainerTransaction)}</li>
+ * <li>{@link SplitPresenter#updateSplitContainer}</li>
* <li>There's a started Activity which matches {@link SplitPairRule} </li>
* <li>Checking whether the place holder should be launched if there's a Activity matches
* {@link SplitPlaceholderRule} </li>
@@ -291,8 +292,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Resets the isolated navigation and updates the container.
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.setTaskFragmentIsolatedNavigation(wct,
- containerToUnpin.getTaskFragmentToken(), false /* isolated */);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin,
+ false /* isolated */);
updateContainer(wct, containerToUnpin);
transactionRecord.apply(false /* shouldApplyIndependently */);
updateCallbackIfNecessary();
@@ -759,6 +760,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (targetContainer == null) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay
+ // container.
targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
}
if (targetContainer == null) {
@@ -877,19 +880,17 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return true;
}
- // Skip resolving if the activity is on a pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
- final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
- if (container != null && taskContainer != null
- && taskContainer.isTaskFragmentContainerPinned(container)) {
+ // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer.
+ if (container != null && container.isIsolatedNavigationEnabled()) {
return true;
}
+ final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
if (!isOnReparent && taskContainer != null
&& taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
!= container) {
// Do not resolve if the launched activity is not the top-most container (excludes
- // the pinned container) in the Task.
+ // the pinned and overlay container) in the Task.
return true;
}
@@ -1007,6 +1008,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (taskContainer == null) {
return;
}
+ // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
final TaskFragmentContainer targetContainer =
taskContainer.getTopNonFinishingTaskFragmentContainer();
if (targetContainer == null) {
@@ -1166,7 +1168,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
&& canReuseContainer(splitRule, splitContainer.getSplitRule(),
- getTaskWindowMetrics(taskProperties.getConfiguration()),
+ taskProperties.getTaskMetrics(),
calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
@@ -1308,15 +1310,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
- // Skip resolving if started from pinned TaskFragmentContainer.
- // TODO(b/243518738): skip resolving for overlay container.
+ // Skip resolving if started from an isolated navigated TaskFragmentContainer.
if (launchingActivity != null) {
final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
launchingActivity);
- final TaskContainer taskContainer =
- taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
- if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
- taskFragmentContainer)) {
+ if (taskFragmentContainer != null
+ && taskFragmentContainer.isIsolatedNavigationEnabled()) {
return null;
}
}
@@ -1408,6 +1407,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
+ return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+ null /* overlayTag */);
+ }
+
+ /**
+ * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
+ * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
+ * overlay container.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createEmptyContainer(
+ @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
+ @NonNull Rect bounds, @Nullable Activity launchingActivity,
+ @Nullable String overlayTag) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1423,13 +1438,46 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Can't find any activity in the Task that we can use as the owner activity.
return null;
}
- final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
- taskId);
- mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
- activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
- mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+ final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
+ intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+ final IBinder taskFragmentToken = container.getTaskFragmentToken();
+ // Note that taskContainer will not exist before calling #newContainer if the container
+ // is the first embedded TF in the task.
+ final TaskContainer taskContainer = container.getTaskContainer();
+ final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ final int windowingMode = taskContainer
+ .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+ mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
+ sanitizedBounds, windowingMode);
+ mPresenter.updateAnimationParams(wct, taskFragmentToken,
TaskFragmentAnimationParams.DEFAULT);
- return expandedContainer;
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
+ overlayTag != null && !sanitizedBounds.isEmpty());
+
+ return container;
+ }
+
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
}
/**
@@ -1449,8 +1497,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
final TaskContainer.TaskProperties taskProperties = mPresenter
.getTaskProperties(primaryActivity);
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
- taskProperties.getConfiguration());
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
getActivityIntentMinDimensionsPair(primaryActivity, intent));
@@ -1519,14 +1566,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId, null /* pairedPrimaryContainer */);
+ activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+ }
+
+ @GuardedBy("mLock")
+ TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
+ @NonNull Activity activityInTask, int taskId,
+ @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+ return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
+ activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
}
/**
@@ -1540,11 +1595,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param taskId parent Task of the new TaskFragment.
* @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
* set, the new container will be added right above it.
+ * @param overlayTag The tag for the new created overlay container. It must be
+ * needed if {@code isOverlay} is {@code true}. Otherwise,
+ * it should be {@code null}.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1553,7 +1611,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
return container;
}
@@ -1693,31 +1751,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
- * Returns the topmost not finished container in Task of given task id.
- */
- @GuardedBy("mLock")
- @Nullable
- TaskFragmentContainer getTopActiveContainer(int taskId) {
- final TaskContainer taskContainer = mTaskContainers.get(taskId);
- if (taskContainer == null) {
- return null;
- }
- final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers();
- for (int i = containers.size() - 1; i >= 0; i--) {
- final TaskFragmentContainer container = containers.get(i);
- if (!container.isFinished() && (container.getRunningActivityCount() > 0
- // We may be waiting for the top TaskFragment to become non-empty after
- // creation. In that case, we don't want to treat the TaskFragment below it as
- // top active, otherwise it may incorrectly launch placeholder on top of the
- // pending TaskFragment.
- || container.isWaitingActivityAppear())) {
- return container;
- }
- }
- return null;
- }
-
- /**
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
@@ -1754,13 +1787,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
* {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
* are {@code null}, the {@link SplitAttributes} will be calculated with
- * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+ * {@link SplitPresenter#computeSplitAttributes}.
*
* @param splitContainer The {@link SplitContainer} to update
* @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
* Otherwise, use the value calculated by
- * {@link SplitPresenter#computeSplitAttributes(
- * TaskContainer.TaskProperties, SplitRule, Pair)}
+ * {@link SplitPresenter#computeSplitAttributes}
*
* @return {@code true} if the update succeed. Otherwise, returns {@code false}.
*/
@@ -1906,7 +1938,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** Whether or not to allow activity in this container to launch placeholder. */
@GuardedBy("mLock")
private boolean allowLaunchPlaceholder(@NonNull TaskFragmentContainer container) {
- final TaskFragmentContainer topContainer = getTopActiveContainer(container.getTaskId());
+ final TaskFragmentContainer topContainer = container.getTaskContainer()
+ .getTopNonFinishingTaskFragmentContainer();
if (container != topContainer) {
// The container is not the top most.
if (!container.isVisible()) {
@@ -2255,6 +2288,96 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
}
+ /**
+ * Gets all overlay containers from all tasks in this process, or an empty list if there's
+ * no overlay container.
+ * <p>
+ * Note that we only support one overlay container for each task, but an app could have multiple
+ * tasks.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @NonNull
+ List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+ final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
+ for (int i = 0; i < mTaskContainers.size(); i++) {
+ final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+ final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+ if (overlayContainer != null) {
+ overlayContainers.add(overlayContainer);
+ }
+ }
+ return overlayContainers;
+ }
+
+ @VisibleForTesting
+ // Suppress GuardedBy warning because lint ask to mark this method as
+ // @GuardedBy(container.mController.mLock), which is mLock itself
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ @Nullable
+ TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+ @NonNull Intent intent, @NonNull Activity launchActivity) {
+ final int taskId = overlayCreateParams.getTaskId();
+ if (taskId != launchTaskId) {
+ // The task ID doesn't match the launch activity's. Cannot determine the host task
+ // to launch the overlay.
+ throw new IllegalArgumentException("The task ID of "
+ + "OverlayCreateParams#launchingActivity must match the task ID of "
+ + "the activity to #startActivity with the activity options that takes "
+ + "OverlayCreateParams.");
+ }
+ final List<TaskFragmentContainer> overlayContainers =
+ getAllOverlayTaskFragmentContainers();
+ final String overlayTag = overlayCreateParams.getTag();
+
+ // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
+ // specified by Intent, expand the overlay container to fill the parent task instead.
+ final Rect bounds = overlayCreateParams.getBounds();
+ final Size minDimensions = getMinDimensions(intent);
+ final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
+ minDimensions);
+ if (!overlayContainers.isEmpty()) {
+ for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+ if (!overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with different tag shown in the same
+ // task, dismiss the existing overlay container.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId != overlayContainer.getTaskId()) {
+ // If there's an overlay container with same tag in a different task,
+ // dismiss the overlay container since the tag must be unique per process.
+ overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+ wct, SplitController.this);
+ }
+ if (overlayTag.equals(overlayContainer.getOverlayTag())
+ && taskId == overlayContainer.getTaskId()) {
+ // If there's an overlay container with the same tag and task ID, we treat
+ // the OverlayCreateParams as the update to the container.
+ final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
+ .getTaskMetrics().getBounds();
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+ final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
+ mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+ !sanitizedBounds.isEmpty());
+ // We can just return the updated overlay container and don't need to
+ // check other condition since we only have one OverlayCreateParams, and
+ // if the tag and task are matched, it's impossible to match another task
+ // or tag since tags and tasks are all unique.
+ return overlayContainer;
+ }
+ }
+ }
+ return createEmptyContainer(wct, intent, taskId,
+ (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+ }
+
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
@@ -2417,8 +2540,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer launchedInTaskFragment;
if (launchingActivity != null) {
final int taskId = getTaskId(launchingActivity);
- launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
- launchingActivity);
+ final OverlayCreateParams overlayCreateParams =
+ OverlayCreateParams.fromBundle(options);
+ if (Flags.activityEmbeddingOverlayPresentationFlag()
+ && overlayCreateParams != null) {
+ launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
+ overlayCreateParams, taskId, intent, launchingActivity);
+ } else {
+ launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+ launchingActivity);
+ }
} else {
launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
intent);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index d894487fafb6..faf7c3999402 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,12 +30,10 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.Pair;
import android.util.Size;
import android.view.View;
-import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentCreationParams;
@@ -307,8 +305,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
final int taskId = primaryContainer.getTaskId();
- final TaskFragmentContainer secondaryContainer = mController.newContainer(
- null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
+ launchingActivity, taskId,
// Pass in the primary container to make sure it is added right above the primary.
primaryContainer);
final TaskContainer taskContainer = mController.getTaskContainer(taskId);
@@ -390,14 +388,27 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return;
}
- setTaskFragmentIsolatedNavigation(wct, secondaryContainer.getTaskFragmentToken(),
- !isStacked /* isolatedNav */);
+ setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */);
if (isStacked && !splitPinRule.isSticky()) {
secondaryContainer.getTaskContainer().removeSplitPinContainer();
}
}
/**
+ * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}
+ */
+ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer taskFragmentContainer,
+ boolean isolatedNavigationEnabled) {
+ if (taskFragmentContainer.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) {
+ return;
+ }
+ taskFragmentContainer.setIsolatedNavigationEnabled(isolatedNavigationEnabled);
+ setTaskFragmentIsolatedNavigation(wct, taskFragmentContainer.getTaskFragmentToken(),
+ isolatedNavigationEnabled);
+ }
+
+ /**
* Resizes the task fragment if it was already registered. Skips the operation if the container
* creation has not been reported from the server yet.
*/
@@ -618,7 +629,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
@Nullable Pair<Size, Size> minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
mController.getSplitAttributesCalculator();
final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +724,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return new Size(windowLayout.minWidth, windowLayout.minHeight);
}
- private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+ static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
@Nullable Size minDimensions) {
if (minDimensions == null) {
return false;
}
+ // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check.
+ if (bounds.isEmpty()) {
+ return false;
+ }
return bounds.width() < minDimensions.getWidth()
|| bounds.height() < minDimensions.getHeight();
}
@@ -1066,14 +1081,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
- return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
- }
-
- @NonNull
- static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
- final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
- // TODO(b/190433398): Supply correct insets.
- final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ return getTaskProperties(activity).getTaskMetrics();
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 9a0769a82d99..9e533808ccc0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -30,7 +30,10 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -61,6 +64,10 @@ class TaskContainer {
@Nullable
private SplitPinContainer mSplitPinContainer;
+ /** The overlay container in this Task. */
+ @Nullable
+ private TaskFragmentContainer mOverlayContainer;
+
@NonNull
private final Configuration mConfiguration;
@@ -184,11 +191,20 @@ class TaskContainer {
@Nullable
TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
+ return getTopNonFinishingTaskFragmentContainer(includePin, false /* includeOverlay */);
+ }
+
+ @Nullable
+ TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin,
+ boolean includeOverlay) {
for (int i = mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = mContainers.get(i);
if (!includePin && isTaskFragmentContainerPinned(container)) {
continue;
}
+ if (!includeOverlay && container.isOverlay()) {
+ continue;
+ }
if (!container.isFinished()) {
return container;
}
@@ -221,6 +237,12 @@ class TaskContainer {
return null;
}
+ /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+ @Nullable
+ TaskFragmentContainer getOverlayContainer() {
+ return mOverlayContainer;
+ }
+
int indexOf(@NonNull TaskFragmentContainer child) {
return mContainers.indexOf(child);
}
@@ -311,8 +333,8 @@ class TaskContainer {
onTaskFragmentContainerUpdated();
}
- void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
- mContainers.removeAll(taskFragmentContainer);
+ void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+ mContainers.removeAll(taskFragmentContainers);
onTaskFragmentContainerUpdated();
}
@@ -332,6 +354,15 @@ class TaskContainer {
}
private void onTaskFragmentContainerUpdated() {
+ // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce
+ // another special container that should also be on top in the future.
+ updateSplitPinContainerIfNecessary();
+ // Update overlay container after split pin container since the overlay should be on top of
+ // pin container.
+ updateOverlayContainerIfNecessary();
+ }
+
+ private void updateSplitPinContainerIfNecessary() {
if (mSplitPinContainer == null) {
return;
}
@@ -344,10 +375,7 @@ class TaskContainer {
}
// Ensure the pinned container is top-most.
- if (pinnedContainerIndex != mContainers.size() - 1) {
- mContainers.remove(pinnedContainer);
- mContainers.add(pinnedContainer);
- }
+ moveContainerToLastIfNecessary(pinnedContainer);
// Update the primary container adjacent to the pinned container if needed.
final TaskFragmentContainer adjacentContainer =
@@ -359,6 +387,31 @@ class TaskContainer {
}
}
+ private void updateOverlayContainerIfNecessary() {
+ final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
+ .filter(TaskFragmentContainer::isOverlay).toList();
+ if (overlayContainers.size() > 1) {
+ throw new IllegalStateException("There must be at most one overlay container per Task");
+ }
+ mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
+ if (mOverlayContainer != null) {
+ moveContainerToLastIfNecessary(mOverlayContainer);
+ }
+ }
+
+ /** Moves the {@code container} to the last to align taskFragments' z-order. */
+ private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
+ final int index = mContainers.indexOf(container);
+ if (index < 0) {
+ Log.w(TAG, "The container:" + container + " is not in the container list!");
+ return;
+ }
+ if (index != mContainers.size() - 1) {
+ mContainers.remove(container);
+ mContainers.add(container);
+ }
+ }
+
/**
* Gets the descriptors of split states in this Task.
*
@@ -398,6 +451,15 @@ class TaskContainer {
return mConfiguration;
}
+ /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */
+ @NonNull
+ WindowMetrics getTaskMetrics() {
+ final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+ // TODO(b/190433398): Supply correct insets.
+ final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+ }
+
/** Translates the given absolute bounds to relative bounds in this Task coordinate. */
void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
if (inOutBounds.isEmpty()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 0a694b5c3b64..3e7f99b96421 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -102,6 +102,9 @@ class TaskFragmentContainer {
*/
private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
+ @Nullable
+ private final String mOverlayTag;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -157,15 +160,32 @@ class TaskFragmentContainer {
*/
private boolean mHasCrossProcessActivities;
+ /** Whether this TaskFragment enable isolated navigation. */
+ private boolean mIsIsolatedNavigationEnabled;
+
+ /**
+ * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
+ * TaskFragmentContainer, String)
+ */
+ TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
+ @Nullable Intent pendingAppearedIntent,
+ @NonNull TaskContainer taskContainer,
+ @NonNull SplitController controller,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
+ controller, pairedPrimaryContainer, null /* overlayTag */);
+ }
+
/**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
* @param pairedPrimaryContainer when it is set, the new container will be add right above it
+ * @param overlayTag Sets to indicate this taskFragment is an overlay container
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
@NonNull SplitController controller,
- @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+ @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -174,6 +194,8 @@ class TaskFragmentContainer {
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
+ mOverlayTag = overlayTag;
+
if (pairedPrimaryContainer != null) {
// The TaskFragment will be positioned right above the paired container.
if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
@@ -786,6 +808,16 @@ class TaskFragmentContainer {
mLastCompanionTaskFragment = fragmentToken;
}
+ /** Returns whether to enable isolated navigation or not. */
+ boolean isIsolatedNavigationEnabled() {
+ return mIsIsolatedNavigationEnabled;
+ }
+
+ /** Sets whether to enable isolated navigation or not. */
+ void setIsolatedNavigationEnabled(boolean isolatedNavigationEnabled) {
+ mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
+ }
+
/**
* Adds the pending appeared activity that has requested to be launched in this task fragment.
* @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
@@ -863,6 +895,20 @@ class TaskFragmentContainer {
return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
}
+ /** Returns whether this taskFragment container is an overlay container. */
+ boolean isOverlay() {
+ return mOverlayTag != null;
+ }
+
+ /**
+ * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
+ * taskFragment container is not an overlay container.
+ */
+ @Nullable
+ String getOverlayTag() {
+ return mOverlayTag;
+ }
+
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);
@@ -881,6 +927,7 @@ class TaskFragmentContainer {
+ " topNonFinishingActivity=" + getTopNonFinishingActivity()
+ " runningActivityCount=" + getRunningActivityCount()
+ " isFinished=" + mIsFinished
+ + " overlayTag=" + mOverlayTag
+ " lastRequestedBounds=" + mLastRequestedBounds
+ " pendingAppearedActivities=" + mPendingAppearedActivities
+ (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index ed2ff2de245b..4ddbd13978d5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
"truth",
"testables",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
new file mode 100644
index 000000000000..405f0b2f51dc
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for overlay presentation feature.
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:OverlayPresentationTest
+ */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OverlayPresentationTest {
+
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
+ private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
+ new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
+
+ private SplitController.ActivityStartMonitor mMonitor;
+
+ private Intent mIntent;
+
+ private TaskFragmentContainer mOverlayContainer1;
+
+ private TaskFragmentContainer mOverlayContainer2;
+
+ private Activity mActivity;
+ @Mock
+ private Resources mActivityResources;
+
+ @Mock
+ private WindowContainerTransaction mTransaction;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+ private SplitController mSplitController;
+ private SplitPresenter mSplitPresenter;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+ .getCurrentWindowLayoutInfo(anyInt(), any());
+ DeviceStateManagerFoldingFeatureProducer producer =
+ mock(DeviceStateManagerFoldingFeatureProducer.class);
+ mSplitController = new SplitController(mWindowLayoutComponent, producer);
+ mSplitPresenter = mSplitController.mPresenter;
+ mMonitor = mSplitController.getActivityStartMonitor();
+ mIntent = new Intent();
+
+ spyOn(mSplitController);
+ spyOn(mSplitPresenter);
+ spyOn(mMonitor);
+
+ doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
+ final Configuration activityConfig = new Configuration();
+ activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+ activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+ doReturn(activityConfig).when(mActivityResources).getConfiguration();
+ doReturn(mHandler).when(mSplitController).getHandler();
+ mActivity = createMockActivity();
+
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ @NonNull
+ private Activity createMockActivity() {
+ final Activity activity = mock(Activity.class);
+ doReturn(mActivityResources).when(activity).getResources();
+ final IBinder activityToken = new Binder();
+ doReturn(activityToken).when(activity).getActivityToken();
+ doReturn(activity).when(mSplitController).getActivity(activityToken);
+ doReturn(TASK_ID).when(activity).getTaskId();
+ doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+ doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+ return activity;
+ }
+
+ @Test
+ public void testOverlayCreateParamsFromBundle() {
+ assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
+
+ assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
+ .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
+ }
+
+ @Test
+ public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
+ mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+ mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+
+ verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
+ anyInt(), any(), any());
+ }
+
+ @NonNull
+ private static Bundle createOverlayCreateParamsTestBundle() {
+ final Bundle bundle = new Bundle();
+
+ final Bundle paramsBundle = new Bundle();
+ paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
+ TEST_OVERLAY_CREATE_PARAMS.getTaskId());
+ paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
+ paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
+ TEST_OVERLAY_CREATE_PARAMS.getBounds());
+
+ bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
+
+ return bundle;
+ }
+
+ @Test
+ public void testGetOverlayContainers() {
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+
+ final TaskFragmentContainer overlayContainer1 =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1);
+
+ assertThrows(
+ "The exception must throw if there are two overlay containers in the same task.",
+ IllegalStateException.class,
+ () -> createTestOverlayContainer(TASK_ID, "test2"));
+
+ final TaskFragmentContainer overlayContainer3 =
+ createTestOverlayContainer(TASK_ID + 1, "test3");
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer1, overlayContainer3);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
+ assertThrows("The method must return null due to task mismatch between"
+ + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
+ () -> createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched to the same task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
+ TASK_ID + 2);
+
+ assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+ + " is launched with the same tag as an existing overlay container in a different "
+ + "task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+ createExistingOverlayContainers();
+
+ final Rect bounds = new Rect(0, 0, 100, 100);
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test1", bounds),
+ TASK_ID);
+
+ assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+ + " is launched with the same tag and task")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(mOverlayContainer1, mOverlayContainer2);
+
+ assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
+ verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
+ eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
+ createExistingOverlayContainers();
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
+ TASK_ID);
+
+ // OverlayContainer1 is dismissed since new container is launched in the same task with
+ // different tag. OverlayContainer2 is dismissed since new container is launched with the
+ // same tag in different task.
+ assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
+ .that(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ private void createExistingOverlayContainers() {
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
+ mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+ List<TaskFragmentContainer> overlayContainers = mSplitController
+ .getAllOverlayTaskFragmentContainers();
+ assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ final Rect bounds = new Rect(TASK_BOUNDS);
+ bounds.offset(10, 10);
+ final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
+ "test", bounds);
+
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ paramsOutsideTaskBounds, TASK_ID);
+ final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+
+ // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+ clearInvocations(mSplitPresenter);
+ createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+
+ verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+ false);
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
+ final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+ TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+ assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+ .containsExactly(overlayContainer);
+ assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
+ assertThat(overlayContainer
+ .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
+ assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+ }
+
+ @Test
+ public void testGetTopNonFishingTaskFragmentContainerWithOverlay() {
+ final TaskFragmentContainer overlayContainer =
+ createTestOverlayContainer(TASK_ID, "test1");
+
+ // Add a SplitPinContainer, the overlay should be on top
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+
+ final TaskFragmentContainer primaryContainer =
+ createMockTaskFragmentContainer(primaryActivity);
+ final TaskFragmentContainer secondaryContainer =
+ createMockTaskFragmentContainer(secondaryActivity);
+ final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+ activityActivityPair -> true /* activityPairPredicate */,
+ activityIntentPair -> true /* activityIntentPairPredicate */,
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.registerSplit(mTransaction, primaryContainer, primaryActivity,
+ secondaryContainer, splitPairRule, splitPairRule.getDefaultSplitAttributes());
+ SplitPinRule splitPinRule = new SplitPinRule.Builder(new SplitAttributes.Builder().build(),
+ parentWindowMetrics -> true /* parentWindowMetricsPredicate */).build();
+ mSplitController.pinTopActivityStack(TASK_ID, splitPinRule);
+ final TaskFragmentContainer topPinnedContainer = mSplitController.getTaskContainer(TASK_ID)
+ .getSplitPinContainer().getSecondaryContainer();
+
+ // Add a normal container after the overlay, the overlay should still on top,
+ // and the SplitPinContainer should also on top of the normal one.
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+
+ assertThat(taskContainer.getTaskFragmentContainers())
+ .containsExactly(primaryContainer, container, secondaryContainer, overlayContainer)
+ .inOrder();
+
+ assertWithMessage("The pinned container must be returned excluding the overlay")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer())
+ .isEqualTo(topPinnedContainer);
+
+ assertThat(taskContainer.getTopNonFinishingTaskFragmentContainer(false))
+ .isEqualTo(container);
+
+ assertWithMessage("The overlay container must be returned since it's always on top")
+ .that(taskContainer.getTopNonFinishingTaskFragmentContainer(
+ false /* includePin */, true /* includeOverlay */))
+ .isEqualTo(overlayContainer);
+ }
+
+ /**
+ * A simplified version of {@link SplitController.ActivityStartMonitor
+ * #createOrUpdateOverlayTaskFragmentIfNeeded}
+ */
+ @Nullable
+ private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+ @NonNull OverlayCreateParams params, int taskId) {
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
+ taskId, mIntent, mActivity);
+ }
+
+ /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
+ @NonNull
+ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
+ setupTaskFragmentInfo(container, activity);
+ return container;
+ }
+
+ @NonNull
+ private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+ TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+ null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
+ null /* pairedPrimaryContainer */, tag);
+ setupTaskFragmentInfo(overlayContainer, mActivity);
+ return overlayContainer;
+ }
+
+ private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+ container.setInfo(mTransaction, info);
+ mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index d440a3eb95de..96839c57d745 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -48,18 +48,18 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealM
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.google.common.truth.Truth.assertWithMessage;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.clearInvocations;
@@ -175,52 +175,6 @@ public class SplitControllerTest {
}
@Test
- public void testGetTopActiveContainer() {
- final TaskContainer taskContainer = createTestTaskContainer();
- // tf1 has no running activity so is not active.
- final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
- // tf2 has running activity so is active.
- final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
- doReturn(1).when(tf2).getRunningActivityCount();
- taskContainer.addTaskFragmentContainer(tf2);
- // tf3 is finished so is not active.
- final TaskFragmentContainer tf3 = mock(TaskFragmentContainer.class);
- doReturn(true).when(tf3).isFinished();
- doReturn(false).when(tf3).isWaitingActivityAppear();
- taskContainer.addTaskFragmentContainer(tf3);
- mSplitController.mTaskContainers.put(TASK_ID, taskContainer);
-
- assertWithMessage("Must return tf2 because tf3 is not active.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf3);
-
- assertWithMessage("Must return tf2 because tf2 has running activity.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf2);
-
- taskContainer.removeTaskFragmentContainer(tf2);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to appear.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
- doReturn(new ArrayList<>()).when(info).getActivities();
- doReturn(true).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
- + " creation.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
-
- doReturn(false).when(info).isEmpty();
- tf1.setInfo(mTransaction, info);
-
- assertWithMessage("Must return null because tf1 becomes empty.")
- .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
- }
-
- @Test
public void testOnTaskFragmentVanished() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
@@ -305,7 +259,9 @@ public class SplitControllerTest {
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).getTopActiveContainer(TASK_ID);
+ TaskContainer taskContainer = tf.getTaskContainer();
+ spyOn(taskContainer);
+ verify(taskContainer, never()).getTopNonFinishingTaskFragmentContainer();
// Verify if tf is not in split, dismissPlaceholderIfNecessary won't be called.
doReturn(false).when(mSplitController).shouldContainerBeExpanded(tf);
@@ -320,7 +276,7 @@ public class SplitControllerTest {
doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
- final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ taskContainer = mSplitController.getTaskContainer(TASK_ID);
taskContainer.addSplitContainer(splitContainer);
// Add a mock SplitContainer on top of splitContainer
final SplitContainer splitContainer2 = mock(SplitContainer.class);
@@ -595,13 +551,12 @@ public class SplitControllerTest {
}
@Test
- public void testResolveStartActivityIntent_skipIfPinned() {
+ public void testResolveStartActivityIntent_skipIfIsolatedNavEnabled() {
final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
- final TaskContainer taskContainer = container.getTaskContainer();
- spyOn(taskContainer);
+ container.setIsolatedNavigationEnabled(true);
+
final Intent intent = new Intent();
setupSplitRule(mActivity, intent);
- doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
mActivity));
}
@@ -634,7 +589,8 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
}
@Test
@@ -796,7 +752,8 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -838,7 +795,8 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+ anyString());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -1565,9 +1523,9 @@ public class SplitControllerTest {
addSplitTaskFragments(primaryActivity, thirdActivity);
// Ensure another SplitContainer is added and the pinned TaskFragment still on top
- assertTrue(taskContainer.getSplitContainers().size() == splitContainerCount + +1);
- assertTrue(mSplitController.getTopActiveContainer(TASK_ID).getTopNonFinishingActivity()
- == secondaryActivity);
+ assertEquals(taskContainer.getSplitContainers().size(), splitContainerCount + +1);
+ assertSame(taskContainer.getTopNonFinishingTaskFragmentContainer()
+ .getTopNonFinishingActivity(), secondaryActivity);
}
/** Creates a mock activity in the organizer process. */
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c72a42cce2bd..fd4522e02438 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -151,6 +151,7 @@ android_library {
static_libs: [
"androidx.appcompat_appcompat",
"androidx.core_core-animation",
+ "androidx.core_core-ktx",
"androidx.arch.core_core-runtime",
"androidx-constraintlayout_constraintlayout",
"androidx.dynamicanimation_dynamicanimation",
@@ -171,4 +172,5 @@ android_library {
kotlincflags: ["-Xjvm-default=all"],
manifest: "AndroidManifest.xml",
plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 3790f04b56eb..d08c573736d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.back;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -70,7 +71,6 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
-
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -317,7 +317,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
(controller) -> controller.registerAnimation(
BackNavigationInfo.TYPE_RETURN_TO_HOME,
- new BackAnimationRunner(callback, runner)));
+ new BackAnimationRunner(
+ callback,
+ runner,
+ controller.mContext,
+ CUJ_PREDICTIVE_BACK_HOME)));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 431df212f099..dc413b059fd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import android.annotation.NonNull;
+import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -27,16 +28,22 @@ import android.view.RemoteAnimationTarget;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.jank.InteractionJankMonitor;
+import com.android.wm.shell.common.InteractionJankMonitorUtils;
+
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
* trigger the real back behavior.
*/
public class BackAnimationRunner {
+ private static final int NO_CUJ = -1;
private static final String TAG = "ShellBackPreview";
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
+ private final @InteractionJankMonitor.CujType int mCujType;
+ private final Context mContext;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
@@ -45,9 +52,21 @@ public class BackAnimationRunner {
private boolean mAnimationCancelled;
public BackAnimationRunner(
- @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) {
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context,
+ @InteractionJankMonitor.CujType int cujType) {
mCallback = callback;
mRunner = runner;
+ mCujType = cujType;
+ mContext = context;
+ }
+
+ public BackAnimationRunner(
+ @NonNull IOnBackInvokedCallback callback,
+ @NonNull IRemoteAnimationRunner runner,
+ @NonNull Context context) {
+ this(callback, runner, context, NO_CUJ);
}
/** Returns the registered animation runner */
@@ -70,10 +89,17 @@ public class BackAnimationRunner {
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.endTracing(mCujType);
+ }
finishedCallback.run();
}
};
mWaitingAnimation = false;
+ if (shouldMonitorCUJ(apps)) {
+ InteractionJankMonitorUtils.beginTracing(
+ mCujType, mContext, apps[0].leash, /* tag */ null);
+ }
try {
getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
@@ -82,6 +108,10 @@ public class BackAnimationRunner {
}
}
+ private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+ return apps.length > 0 && mCujType != NO_CUJ;
+ }
+
void startGesture() {
mWaitingAnimation = true;
mAnimationCancelled = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 114486e848f0..24479d7b5f39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.back.BackAnimationConstants.PROGRESS_COMMIT_THRESHOLD;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -135,7 +136,8 @@ public class CrossActivityAnimation extends ShellBackAnimation {
@Inject
public CrossActivityAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mBackground = background;
mEnteringProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
mEnteringProgressSpring.setSpring(new SpringForce()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 209d8533ee7d..fc5ff017ebe5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -20,6 +20,7 @@ import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.window.BackEvent.EDGE_RIGHT;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_TASK;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -108,6 +109,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private final float[] mTmpTranslate = {0, 0, 0};
private final BackAnimationRunner mBackAnimationRunner;
private final BackAnimationBackground mBackground;
+ private final Context mContext;
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -120,8 +122,10 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
@Inject
public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+ mContext = context;
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK);
mBackground = background;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index aca638c1a5cf..5254ff466123 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.back;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import android.animation.Animator;
@@ -97,7 +98,8 @@ public class CustomizeActivityAnimation extends ShellBackAnimation {
SurfaceControl.Transaction transaction, Choreographer choreographer) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mBackground = background;
- mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackAnimationRunner = new BackAnimationRunner(
+ new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY);
mCustomAnimationLoader = new CustomAnimationLoader(context);
mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
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 dddcbd4c96c0..f0da35df39ee 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
@@ -317,7 +317,8 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleIconFactory = new BubbleIconFactory(context,
context.getResources().getDimensionPixelSize(R.dimen.bubble_size),
context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- context.getResources().getColor(R.color.important_conversation),
+ context.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mDisplayController = displayController;
@@ -949,7 +950,8 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
@@ -988,7 +990,8 @@ public class BubbleController implements ConfigurationChangeListener,
mBubbleIconFactory = new BubbleIconFactory(mContext,
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size),
mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size),
- mContext.getResources().getColor(R.color.important_conversation),
+ mContext.getResources().getColor(
+ com.android.launcher3.icons.R.color.important_conversation),
mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.importance_ring_stroke_width));
mStackView.onDisplaySizeChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index dc099d9abda4..22e836aacfc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -113,7 +113,7 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
context,
res.getDimensionPixelSize(R.dimen.bubble_size),
res.getDimensionPixelSize(R.dimen.bubble_badge_size),
- res.getColor(R.color.important_conversation),
+ res.getColor(com.android.launcher3.icons.R.color.important_conversation),
res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width)
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 738c94e82a95..79f306ece283 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -19,6 +19,7 @@ package com.android.wm.shell.bubbles.animation;
import static android.view.View.LAYOUT_DIRECTION_RTL;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.res.Resources;
import android.graphics.Path;
@@ -375,6 +376,9 @@ public class ExpandedAnimationController
mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ int screenWidthPx = mLayout.getContext().getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedBubbleDraggingOut.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
private void springBubbleTo(View bubble, float x, float y) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
new file mode 100644
index 000000000000..2a44f04f1358
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/FlingToDismissUtils.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles.animation
+
+/** Utils related to the fling to dismiss animation. */
+object FlingToDismissUtils {
+
+ /** The target width surrounding the dismiss target on a small width screen, e.g. phone. */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_SMALL = 3f
+ /**
+ * The target width surrounding the dismiss target on a medium width screen, e.g. tablet in
+ * portrait.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM = 4.5f
+ /**
+ * The target width surrounding the dismiss target on a large width screen, e.g. tablet in
+ * landscape.
+ */
+ private const val FLING_TO_DISMISS_TARGET_WIDTH_LARGE = 6f
+
+ /** Returns the dismiss target width for the specified [screenWidthPx]. */
+ @JvmStatic
+ fun getFlingToDismissTargetWidth(screenWidthPx: Int) = when {
+ screenWidthPx >= 2000 -> FLING_TO_DISMISS_TARGET_WIDTH_LARGE
+ screenWidthPx >= 1500 -> FLING_TO_DISMISS_TARGET_WIDTH_MEDIUM
+ else -> FLING_TO_DISMISS_TARGET_WIDTH_SMALL
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index aad268394305..e48732801094 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.bubbles.animation;
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.animation.FlingToDismissUtils.getFlingToDismissTargetWidth;
import android.content.ContentResolver;
import android.content.res.Resources;
@@ -851,6 +852,15 @@ public class StackAnimationController extends
if (mLayout != null) {
Resources res = mLayout.getContext().getResources();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ updateFlingToDismissTargetWidth();
+ }
+ }
+
+ private void updateFlingToDismissTargetWidth() {
+ if (mLayout != null && mMagnetizedStack != null) {
+ int screenWidthPx = mLayout.getResources().getDisplayMetrics().widthPixels;
+ mMagnetizedStack.setFlingToTargetWidthPercent(
+ getFlingToDismissTargetWidth(screenWidthPx));
}
}
@@ -1022,23 +1032,8 @@ public class StackAnimationController extends
};
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
+ updateFlingToDismissTargetWidth();
}
-
- final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float minVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_fling_min_velocity",
- mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
- final float maxVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_stick_max_velocity",
- mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
- final float targetWidth = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_target_width_percent",
- mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
-
- mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
- mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
- mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
-
return mMagnetizedStack;
}
@@ -1053,7 +1048,7 @@ public class StackAnimationController extends
* property directly to move the first bubble and cause the stack to 'follow' to the new
* location.
*
- * This could also be achieved by simply animating the first bubble view and adding an update
+ * <p>This could also be achieved by simply animating the first bubble view and adding an update
* listener to dispatch movement to the rest of the stack. However, this would require
* duplication of logic in that update handler - it's simpler to keep all logic contained in the
* {@link #moveFirstBubbleWithStackFollowing} method.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
new file mode 100644
index 000000000000..f561aa5568be
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/HomeTransitionObserver.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+
+import static com.android.wm.shell.transition.Transitions.TransitionObserver;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.common.RemoteCallable;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
+import com.android.wm.shell.util.TransitionUtil;
+
+/**
+ * The {@link TransitionObserver} that observes for transitions involving the home
+ * activity. It reports transitions to the caller via {@link IHomeTransitionListener}.
+ */
+public class HomeTransitionObserver implements TransitionObserver,
+ RemoteCallable<HomeTransitionObserver> {
+ private final SingleInstanceRemoteListener<HomeTransitionObserver, IHomeTransitionListener>
+ mListener;
+
+ private @NonNull final Context mContext;
+ private @NonNull final ShellExecutor mMainExecutor;
+ private @NonNull final Transitions mTransitions;
+
+ public HomeTransitionObserver(@NonNull Context context,
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull Transitions transitions) {
+ mContext = context;
+ mMainExecutor = mainExecutor;
+ mTransitions = transitions;
+
+ mListener = new SingleInstanceRemoteListener<>(this,
+ c -> mTransitions.registerObserver(this),
+ c -> mTransitions.unregisterObserver(this));
+
+ }
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue;
+ }
+
+ final int mode = change.getMode();
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME
+ && TransitionUtil.isOpenOrCloseMode(mode)) {
+ mListener.call(l -> l.onHomeVisibilityChanged(TransitionUtil.isOpeningType(mode)));
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {}
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged,
+ @NonNull IBinder playing) {}
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition,
+ boolean aborted) {}
+
+ /**
+ * Sets the home transition listener that receives any transitions resulting in a change of
+ *
+ */
+ public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ if (listener != null) {
+ mListener.register(listener);
+ } else {
+ mListener.unregister();
+ }
+ }
+
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Invalidates this controller, preventing future calls to send updates.
+ */
+ public void invalidate() {
+ mTransitions.unregisterObserver(this);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
index 6575cdd69c93..18716c68da27 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/QSTilesDefaultLog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IHomeTransitionListener.aidl
@@ -13,16 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.log.dagger
-import javax.inject.Qualifier
+package com.android.wm.shell.transition;
+
+import android.window.RemoteTransition;
+import android.window.TransitionFilter;
/**
- * A default [com.android.systemui.log.LogBuffer] for QS tiles messages. It's used exclusively in
- * [com.android.systemui.qs.tiles.base.logging.QSTileLogger]. If you need to increase it for you
- * tile, add one to the map provided by the [QSTilesLogBuffers]
+ * Listener interface that Launcher attaches to SystemUI to get home activity transition callbacks.
*/
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class QSTilesDefaultLog
+interface IHomeTransitionListener {
+
+ /**
+ * Called when a transition changes the visibility of the home activity.
+ */
+ void onHomeVisibilityChanged(in boolean isVisible);
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
index cc4d268a0000..644a6a5114a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/IShellTransitions.aidl
@@ -19,6 +19,8 @@ package com.android.wm.shell.transition;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
+import com.android.wm.shell.transition.IHomeTransitionListener;
+
/**
* Interface that is exposed to remote callers to manipulate the transitions feature.
*/
@@ -39,4 +41,7 @@ interface IShellTransitions {
* Retrieves the apply-token used by transactions in Shell
*/
IBinder getShellApplyToken() = 3;
+
+ /** Set listener that will receive callbacks about transitions involving home activity */
+ oneway void setHomeTransitionListener(in IHomeTransitionListener listener) = 4;
}
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 576bba96044c..baa9acaafa4b 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
@@ -356,7 +356,7 @@ public class Transitions implements RemoteCallable<Transitions>,
}
private ExternalInterfaceBinder createExternalInterface() {
- return new IShellTransitionsImpl(this);
+ return new IShellTransitionsImpl(mContext, getMainExecutor(), this);
}
@Override
@@ -1400,9 +1400,12 @@ public class Transitions implements RemoteCallable<Transitions>,
private static class IShellTransitionsImpl extends IShellTransitions.Stub
implements ExternalInterfaceBinder {
private Transitions mTransitions;
+ private final HomeTransitionObserver mHomeTransitionObserver;
- IShellTransitionsImpl(Transitions transitions) {
+ IShellTransitionsImpl(Context context, ShellExecutor executor, Transitions transitions) {
mTransitions = transitions;
+ mHomeTransitionObserver = new HomeTransitionObserver(context, executor,
+ mTransitions);
}
/**
@@ -1410,6 +1413,7 @@ public class Transitions implements RemoteCallable<Transitions>,
*/
@Override
public void invalidate() {
+ mHomeTransitionObserver.invalidate();
mTransitions = null;
}
@@ -1434,6 +1438,14 @@ public class Transitions implements RemoteCallable<Transitions>,
public IBinder getShellApplyToken() {
return SurfaceControl.Transaction.getDefaultApplyToken();
}
+
+ @Override
+ public void setHomeTransitionListener(IHomeTransitionListener listener) {
+ executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
+ (transitions) -> {
+ mHomeTransitionObserver.setHomeTransitionListener(listener);
+ });
+ }
}
private class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 248e83747c48..bb262d3df07f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -116,7 +116,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
DesktopModeWindowDecoration(
@@ -133,10 +134,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
- windowContainerTransactionSupplier, surfaceControlViewHostFactory);
+ windowContainerTransactionSupplier, surfaceControlSupplier,
+ surfaceControlViewHostFactory);
mHandler = handler;
mChoreographer = choreographer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 389db62ef9d2..dadd264596fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
@@ -45,6 +46,7 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
@@ -78,7 +80,10 @@ class FluidResizeTaskPositioner implements DragPositioningCallback {
mTaskOrganizer.applyTransaction(wct);
}
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 303954a2d9fb..852c037baad5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.IBinder;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -58,6 +59,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
+ @Surface.Rotation private int mRotation;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
@@ -98,7 +100,10 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
}
mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
- if (mStableBounds.isEmpty()) {
+ int rotation = mDesktopWindowDecoration
+ .mTaskInfo.configuration.windowConfiguration.getDisplayRotation();
+ if (mStableBounds.isEmpty() || mRotation != rotation) {
+ mRotation = rotation;
mDisplayController.getDisplayLayout(mDesktopWindowDecoration.mDisplay.getDisplayId())
.getStableBounds(mStableBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index d0e647b30a96..044c0331282c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowInsets.Type.statusBars;
+import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
@@ -137,7 +138,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
Configuration windowDecorConfig) {
this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
- WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
+ WindowContainerTransaction::new, SurfaceControl::new,
+ new SurfaceControlViewHostFactory() {});
}
WindowDecoration(
@@ -145,17 +147,18 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
DisplayController displayController,
ShellTaskOrganizer taskOrganizer,
RunningTaskInfo taskInfo,
- SurfaceControl taskSurface,
+ @NonNull SurfaceControl taskSurface,
Configuration windowDecorConfig,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
mContext = context;
mDisplayController = displayController;
mTaskOrganizer = taskOrganizer;
mTaskInfo = taskInfo;
- mTaskSurface = taskSurface;
+ mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
@@ -453,6 +456,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
public void close() {
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
releaseViews();
+ mTaskSurface.release();
}
static int loadDimensionPixelSize(Resources resources, int resourceId) {
@@ -469,6 +473,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return resources.getDimension(resourceId);
}
+ private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
+ Supplier<SurfaceControl> surfaceControlSupplier) {
+ final SurfaceControl copy = surfaceControlSupplier.get();
+ copy.copyFrom(sc, "WindowDecoration");
+ return copy;
+ }
+
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 7f020725d61f..acfb259d7359 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -39,7 +39,25 @@ filegroup {
}
filegroup {
- name: "WMShellFlickerTestsPip-src",
+ name: "WMShellFlickerTestsPip1-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/pip/A*.kt",
+ "src/com/android/wm/shell/flicker/pip/B*.kt",
+ "src/com/android/wm/shell/flicker/pip/C*.kt",
+ "src/com/android/wm/shell/flicker/pip/D*.kt",
+ "src/com/android/wm/shell/flicker/pip/S*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip2-src",
+ srcs: [
+ "src/com/android/wm/shell/flicker/pip/E*.kt",
+ ],
+}
+
+filegroup {
+ name: "WMShellFlickerTestsPip3-src",
srcs: ["src/com/android/wm/shell/flicker/pip/*.kt"],
}
@@ -176,7 +194,9 @@ android_test {
],
exclude_srcs: [
":WMShellFlickerTestsBubbles-src",
- ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ":WMShellFlickerTestsPip3-src",
":WMShellFlickerTestsPipCommon-src",
":WMShellFlickerTestsPipApps-src",
":WMShellFlickerTestsSplitScreenGroup1-src",
@@ -200,19 +220,49 @@ android_test {
}
android_test {
- name: "WMShellFlickerTestsPip",
+ name: "WMShellFlickerTestsPip1",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+}
+
+android_test {
+ name: "WMShellFlickerTestsPip2",
defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestPip.xml"],
package_name: "com.android.wm.shell.flicker.pip",
instrumentation_target_package: "com.android.wm.shell.flicker.pip",
srcs: [
":WMShellFlickerTestsBase-src",
- ":WMShellFlickerTestsPip-src",
+ ":WMShellFlickerTestsPip2-src",
":WMShellFlickerTestsPipCommon-src",
],
}
android_test {
+ name: "WMShellFlickerTestsPip3",
+ defaults: ["WMShellFlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestPip.xml"],
+ package_name: "com.android.wm.shell.flicker.pip",
+ instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+ srcs: [
+ ":WMShellFlickerTestsBase-src",
+ ":WMShellFlickerTestsPip3-src",
+ ":WMShellFlickerTestsPipCommon-src",
+ ],
+ exclude_srcs: [
+ ":WMShellFlickerTestsPip1-src",
+ ":WMShellFlickerTestsPip2-src",
+ ],
+}
+
+android_test {
name: "WMShellFlickerTestsPipApps",
defaults: ["WMShellFlickerTestsDefault"],
additional_manifests: ["manifests/AndroidManifestPip.xml"],
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 19c8435facaf..94e3959782ed 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -22,6 +22,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -55,7 +56,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class AutoEnterPipOnGoToHomeTest(flicker: LegacyFlickerTest) :
- EnterPipViaAppUiButtonTest(flicker) {
+ EnterPipTransition(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = { transitions { tapl.goHome() } }
override val defaultEnterPip: FlickerBuilder.() -> Unit = {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 2cd08a4a58a6..596580547d59 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -26,6 +26,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -66,8 +67,10 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
standardAppHelper.launchViaIntent(
wmHelper,
NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
- ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME,
- NetflixAppHelper.WATCH_ACTIVITY)
+ ComponentNameMatcher(
+ NetflixAppHelper.PACKAGE_NAME,
+ NetflixAppHelper.WATCH_ACTIVITY
+ )
)
standardAppHelper.waitForVideoPlaying()
}
@@ -99,6 +102,41 @@ open class NetflixEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransit
super.focusChanges()
}
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ // Netflix starts in immersive fullscreen mode, so taskbar bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.TASK_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ }
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ // Netflix starts in immersive fullscreen mode, so status bar is not visible at start
+ flicker.statusBarLayerPositionAtEnd()
+ }
+
+ @Postsubmit
+ @Test override fun statusBarWindowIsAlwaysVisible() {
+ // Netflix plays in immersive fullscreen mode, so taskbar will be gone at some point
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index e7d0f601ff5a..d6141cfd21ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -568,8 +568,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
private void registerAnimation(int type) {
- mController.registerAnimation(type,
- new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
+ mController.registerAnimation(
+ type,
+ new BackAnimationRunner(
+ mAnimatorCallback,
+ mBackAnimationRunner,
+ mContext));
}
private void unregisterAnimation(int type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index a2dbab197fb5..18fcdd00df9d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -111,7 +111,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
- WindowContainerTransaction::new, mMockSurfaceControlViewHostFactory);
+ WindowContainerTransaction::new, SurfaceControl::new,
+ mMockSurfaceControlViewHostFactory);
}
private ActivityManager.RunningTaskInfo createTaskInfo(boolean visible) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index c0c4498e3ebf..add78b2ee8b3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -6,6 +6,9 @@ import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
@@ -24,6 +27,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
@@ -76,7 +80,15 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
@@ -89,6 +101,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -623,7 +636,7 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -641,9 +654,81 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ })
+ // Drag back to starting bounds.
+ performDrag(
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = Surface.ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
}
})
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
}
companion object {
@@ -664,11 +749,17 @@ class FluidResizeTaskPositionerTest : ShellTestCase() {
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 8913453aa578..a70ebf14324a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -21,6 +21,9 @@ import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.WindowContainerToken
@@ -30,6 +33,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
@@ -93,10 +97,17 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
- (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ if (mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
}
`when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
-
mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
token = taskToken
@@ -105,6 +116,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
defaultMinSize = DEFAULT_MIN
displayId = DISPLAY_ID
configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
}
mockDesktopWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
@@ -343,7 +355,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
)
val newX = STARTING_BOUNDS.left.toFloat()
- val newY = STABLE_BOUNDS.top.toFloat() - 5
+ val newY = STABLE_BOUNDS_LANDSCAPE.top.toFloat() - 5
taskPositioner.onDragPositioningMove(
newX,
newY
@@ -361,11 +373,79 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS.top
+ STABLE_BOUNDS_LANDSCAPE.top
}
})
}
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() {
+ // Test landscape stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Drag back to starting bounds.
+ performDrag(STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockDesktopWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
+ rectAfterDrag.right -= 2000
+ rectAfterDrag.bottom += 2000
+
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag}},
+ eq(taskPositioner))
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ }
+
+ private fun performDrag(
+ startX: Float,
+ startY: Float,
+ endX: Float,
+ endY: Float,
+ ctrlType: Int
+ ) {
+ taskPositioner.onDragPositioningStart(
+ ctrlType,
+ startX,
+ startY
+ )
+ taskPositioner.onDragPositioningMove(
+ endX,
+ endY
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ endX,
+ endY
+ )
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -378,11 +458,17 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
- private val STABLE_BOUNDS = Rect(
+ private val STABLE_BOUNDS_LANDSCAPE = Rect(
DISPLAY_BOUNDS.left,
DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
DISPLAY_BOUNDS.right,
DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
)
+ private val STABLE_BOUNDS_PORTRAIT = Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT
+ )
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 8061aa3f844a..8e42f74b8d17 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -121,6 +121,8 @@ public class WindowDecorationTests extends ShellTestCase {
private WindowContainerTransaction mMockWindowContainerTransaction;
@Mock
private SurfaceSyncGroup mMockSurfaceSyncGroup;
+ @Mock
+ private SurfaceControl mMockTaskSurface;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
@@ -189,8 +191,7 @@ public class WindowDecorationTests extends ShellTestCase {
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -199,7 +200,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder, never()).build();
verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
- verify(mMockSurfaceControlFinishT).hide(taskSurface);
+ verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
assertNull(mRelayoutResult.mRootView);
}
@@ -229,12 +230,11 @@ public class WindowDecorationTests extends ShellTestCase {
taskInfo.isFocused = true;
// Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+ verify(decorContainerSurfaceBuilder).setParent(mMockTaskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
@@ -262,14 +262,15 @@ public class WindowDecorationTests extends ShellTestCase {
}
verify(mMockSurfaceControlFinishT)
- .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+ .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
+ TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setWindowCrop(taskSurface, 300, 100);
- verify(mMockSurfaceControlStartT).setCornerRadius(taskSurface, CORNER_RADIUS);
- verify(mMockSurfaceControlFinishT).setCornerRadius(taskSurface, CORNER_RADIUS);
+ .setWindowCrop(mMockTaskSurface, 300, 100);
+ verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
+ verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlStartT)
- .show(taskSurface);
- verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10);
+ .show(mMockTaskSurface);
+ verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
assertEquals(300, mRelayoutResult.mWidth);
assertEquals(100, mRelayoutResult.mHeight);
@@ -308,8 +309,7 @@ public class WindowDecorationTests extends ShellTestCase {
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -346,8 +346,7 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(true)
.build();
- final TestWindowDecoration windowDecor =
- createWindowDecoration(taskInfo, new SurfaceControl());
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
// It shouldn't show the window decoration when it can't obtain the display instance.
@@ -405,8 +404,7 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
@@ -465,8 +463,7 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -506,8 +503,7 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */);
@@ -545,12 +541,11 @@ public class WindowDecorationTests extends ShellTestCase {
.setWindowingMode(WINDOWING_MODE_FREEFORM)
.build();
taskInfo.isFocused = true;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlStartT).setColor(taskSurface, new float[]{1.f, 1.f, 0.f});
+ verify(mMockSurfaceControlStartT).setColor(mMockTaskSurface, new float[]{1.f, 1.f, 0.f});
mockitoSession.finishMocking();
}
@@ -568,8 +563,7 @@ public class WindowDecorationTests extends ShellTestCase {
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(true)
.build();
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
assertTrue(mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars())
.isVisible());
@@ -613,12 +607,11 @@ public class WindowDecorationTests extends ShellTestCase {
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.build();
taskInfo.isFocused = true;
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
- verify(mMockSurfaceControlStartT).unsetColor(taskSurface);
+ verify(mMockSurfaceControlStartT).unsetColor(mMockTaskSurface);
mockitoSession.finishMocking();
}
@@ -639,8 +632,7 @@ public class WindowDecorationTests extends ShellTestCase {
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(true)
.build();
- final SurfaceControl taskSurface = mock(SurfaceControl.class);
- final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
windowDecor.relayout(taskInfo);
@@ -650,15 +642,15 @@ public class WindowDecorationTests extends ShellTestCase {
eq(0) /* index */, eq(mandatorySystemGestures()));
}
- private TestWindowDecoration createWindowDecoration(
- ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
+ private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
- taskInfo, testSurface, mWindowConfiguration,
+ taskInfo, mMockTaskSurface, mWindowConfiguration,
new MockObjectSupplier<>(mMockSurfaceControlBuilders,
() -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))),
new MockObjectSupplier<>(mMockSurfaceControlTransactions,
() -> mock(SurfaceControl.Transaction.class)),
- () -> mMockWindowContainerTransaction, mMockSurfaceControlViewHostFactory);
+ () -> mMockWindowContainerTransaction, () -> mMockTaskSurface,
+ mMockSurfaceControlViewHostFactory);
}
private class MockObjectSupplier<T> implements Supplier<T> {
@@ -697,11 +689,12 @@ public class WindowDecorationTests extends ShellTestCase {
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
+ Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
windowConfiguration, surfaceControlBuilderSupplier,
surfaceControlTransactionSupplier, windowContainerTransactionSupplier,
- surfaceControlViewHostFactory);
+ surfaceControlSupplier, surfaceControlViewHostFactory);
}
@Override
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 7f226939ffaa..d056248273b2 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
has_locale = true;
}
- // if we don't have a result yet
+ // if we don't have a result yet
if (!final_result ||
// or this config is better before the locale than the existing result
result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
@@ -863,9 +863,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
// We can skip calling ResTable_config::match() if the caller does not care for the
// configuration to match or if we're using the list of types that have already had their
- // configuration matched.
+ // configuration matched. The exception to this is when the user has multiple locales set
+ // because the filtered list will then have values from multiple locales and we will need to
+ // call match() to make sure the current entry matches the config we are currently checking.
const ResTable_config& this_config = type_entry->config;
- if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) {
+ if (!((use_filtered && (configurations_.size() == 1))
+ || ignore_configuration || this_config.match(desired_config))) {
continue;
}
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index e672b983d509..e986c38a845a 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -27,3 +27,10 @@ flag {
description: "APIs to create a new gainmap with a bitmap for metadata."
bug: "304478551"
}
+
+flag {
+ name: "clip_surfaceviews"
+ namespace: "core_graphics"
+ description: "Clip z-above surfaceviews to global clip rect"
+ bug: "298621623"
+}
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 10427039c35a..814b682a2b98 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@ SkiaMemoryTracer::SkiaMemoryTracer(const char* categoryKey, bool itemizeType)
, mTotalSize("bytes", 0)
, mPurgeableSize("bytes", 0) {}
-const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) {
for (auto& resource : mResourceMap) {
- if (SkStrContains(resourceName, resource.first)) {
+ if (resourceName.find(resource.first) != std::string::npos) {
return resource.second;
}
}
- return nullptr;
+ return std::nullopt;
}
void SkiaMemoryTracer::processElement() {
@@ -62,7 +62,7 @@ void SkiaMemoryTracer::processElement() {
}
// find the type if one exists
- const char* type;
+ std::string type;
auto typeResult = mCurrentValues.find("type");
if (typeResult != mCurrentValues.end()) {
type = typeResult->second.units;
@@ -71,14 +71,13 @@ void SkiaMemoryTracer::processElement() {
}
// compute the type if we are itemizing or use the default "size" if we are not
- const char* key = (mItemizeType) ? type : sizeResult->first;
- SkASSERT(key != nullptr);
+ std::string key = (mItemizeType) ? type : sizeResult->first;
// compute the top level element name using either the map or category key
- const char* resourceName = mapName(mCurrentElement.c_str());
- if (mCategoryKey != nullptr) {
+ std::optional<std::string> resourceName = mapName(mCurrentElement);
+ if (mCategoryKey) {
// find the category if one exists
- auto categoryResult = mCurrentValues.find(mCategoryKey);
+ auto categoryResult = mCurrentValues.find(*mCategoryKey);
if (categoryResult != mCurrentValues.end()) {
resourceName = categoryResult->second.units;
} else if (mItemizeType) {
@@ -87,11 +86,11 @@ void SkiaMemoryTracer::processElement() {
}
// if we don't have a pretty name then use the dumpName
- if (resourceName == nullptr) {
- resourceName = mCurrentElement.c_str();
+ if (!resourceName) {
+ resourceName = mCurrentElement;
}
- auto result = mResults.find(resourceName);
+ auto result = mResults.find(*resourceName);
if (result != mResults.end()) {
auto& resourceValues = result->second;
typeResult = resourceValues.find(key);
@@ -106,7 +105,7 @@ void SkiaMemoryTracer::processElement() {
TraceValue sizeValue = sizeResult->second;
mCurrentValues.clear();
mCurrentValues.insert({key, sizeValue});
- mResults.insert({resourceName, mCurrentValues});
+ mResults.insert({*resourceName, mCurrentValues});
}
}
@@ -139,8 +138,9 @@ void SkiaMemoryTracer::logOutput(String8& log) {
for (const auto& typedValue : namedItem.second) {
TraceValue traceValue = convertUnits(typedValue.second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
- log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
- traceValue.units, traceValue.count, entry);
+ log.appendFormat(" %s: %.2f %s (%d %s)\n", typedValue.first.c_str(),
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
} else {
auto result = namedItem.second.find("size");
@@ -148,7 +148,8 @@ void SkiaMemoryTracer::logOutput(String8& log) {
TraceValue traceValue = convertUnits(result->second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
log.appendFormat(" %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
- traceValue.value, traceValue.units, traceValue.count, entry);
+ traceValue.value, traceValue.units.c_str(), traceValue.count,
+ entry);
}
}
}
@@ -156,7 +157,7 @@ void SkiaMemoryTracer::logOutput(String8& log) {
size_t SkiaMemoryTracer::total() {
processElement();
- if (!strcmp("bytes", mTotalSize.units)) {
+ if ("bytes" == mTotalSize.units) {
return mTotalSize.value;
}
return 0;
@@ -166,16 +167,16 @@ void SkiaMemoryTracer::logTotals(String8& log) {
TraceValue total = convertUnits(mTotalSize);
TraceValue purgeable = convertUnits(mPurgeableSize);
log.appendFormat(" %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
- total.value, total.units, purgeable.value, purgeable.units);
+ total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
}
SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
TraceValue output(value);
- if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+ if ("bytes" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "KB";
}
- if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+ if ("KB" == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "MB";
}
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index cba3b0422c6f..dbfc86bc033c 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -16,8 +16,9 @@
#pragma once
-#include <SkString.h>
#include <SkTraceMemoryDump.h>
+#include <optional>
+#include <string>
#include <utils/String8.h>
#include <unordered_map>
#include <vector>
@@ -60,17 +61,17 @@ private:
TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
- const char* units;
+ std::string units;
float value;
int count;
};
- const char* mapName(const char* resourceName);
+ std::optional<std::string> mapName(const std::string& resourceName);
void processElement();
TraceValue convertUnits(const TraceValue& value);
const std::vector<ResourcePair> mResourceMap;
- const char* mCategoryKey = nullptr;
+ std::optional<std::string> mCategoryKey;
const bool mItemizeType;
// variables storing the size of all elements being dumped
@@ -79,12 +80,12 @@ private:
// variables storing information on the current node being dumped
std::string mCurrentElement;
- std::unordered_map<const char*, TraceValue> mCurrentValues;
+ std::unordered_map<std::string, TraceValue> mCurrentValues;
// variable that stores the final format of the data after the individual elements are processed
- std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+ std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults;
};
} /* namespace skiapipeline */
} /* namespace uirenderer */
-} /* namespace android */ \ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index b00fc699e515..7fac0c9776ac 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -139,6 +139,7 @@ CanvasContext::~CanvasContext() {
mRenderNodes.clear();
mRenderThread.cacheManager().unregisterCanvasContext(this);
mRenderThread.renderState().removeContextCallback(this);
+ mHintSessionWrapper->destroy();
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -561,7 +562,11 @@ Frame CanvasContext::getFrame() {
void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
- LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+ if (grContext->isDeviceLost()) {
+ LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+ return;
+ }
+ LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
return;
}
}
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 1c3399a6650d..2362331aca26 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -158,7 +158,6 @@ void HintSessionWrapper::sendLoadResetHint() {
void HintSessionWrapper::sendLoadIncreaseHint() {
if (!init()) return;
mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
- mLastFrameNotification = systemTime();
}
bool HintSessionWrapper::alive() {
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index a14ae1cc46ec..10a740a1f803 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -259,6 +259,31 @@ TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishe
TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+ EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
+ EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, report an actual duration
+ mWrapper->reportActualWorkDuration(5_ms);
+
+ // Then, run the delayed deletion after sending the update
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close within the timeframe of the test
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
EXPECT_CALL(*sMockBinding,
fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
.Times(1);
@@ -272,16 +297,46 @@ TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
// First schedule the deletion
scheduleDelayedDestroyManaged();
- // Then, send a hint to update the timestamp
+ // Then, send a load_up hint
mWrapper->sendLoadIncreaseHint();
// Then, run the delayed deletion after sending the update
allowDelayedDestructionToStart();
waitForDelayedDestructionToFinish();
- // Ensure it didn't close within the timeframe of the test
+ // Ensure it closed within the timeframe of the test
Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+ mWrapper->init();
+ waitForWrapperReady();
+ // Init a second time just to grab the wrapper from the promise
+ mWrapper->init();
EXPECT_EQ(mWrapper->alive(), true);
+
+ // First schedule the deletion
+ scheduleDelayedDestroyManaged();
+
+ // Then, kill the session
+ mWrapper->destroy();
+
+ // Verify it died
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
+
+ EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+ // Then, run the delayed deletion after manually killing the session
+ allowDelayedDestructionToStart();
+ waitForDelayedDestructionToFinish();
+
+ // Ensure it didn't close again and is still dead
+ Mock::VerifyAndClearExpectations(sMockBinding.get());
+ EXPECT_EQ(mWrapper->alive(), false);
}
} // namespace android::uirenderer::renderthread \ No newline at end of file
diff --git a/location/api/current.txt b/location/api/current.txt
index 33effdd6cd6c..0c23d8cd77e0 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -412,7 +412,9 @@ package android.location {
field public static final int TYPE_GPS_L1CA = 257; // 0x101
field public static final int TYPE_GPS_L2CNAV = 258; // 0x102
field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
- field public static final int TYPE_IRN_L5CA = 1793; // 0x701
+ field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
+ field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
+ field @Deprecated public static final int TYPE_IRN_L5CA = 1793; // 0x701
field public static final int TYPE_QZS_L1CA = 1025; // 0x401
field public static final int TYPE_SBS = 513; // 0x201
field public static final int TYPE_UNKNOWN = 0; // 0x0
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 637f90536125..32e636f8658b 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -16,10 +16,12 @@
package android.location;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.TestApi;
+import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,7 +43,7 @@ public final class GnssNavigationMessage implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNKNOWN, TYPE_GPS_L1CA, TYPE_GPS_L2CNAV, TYPE_GPS_L5CNAV, TYPE_GPS_CNAV2,
TYPE_SBS, TYPE_GLO_L1CA, TYPE_QZS_L1CA, TYPE_BDS_D1, TYPE_BDS_D2, TYPE_BDS_CNAV1,
- TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA})
+ TYPE_BDS_CNAV2, TYPE_GAL_I, TYPE_GAL_F, TYPE_IRN_L5CA, TYPE_IRN_L5, TYPE_IRN_L1})
public @interface GnssNavigationMessageType {}
// The following enumerations must be in sync with the values declared in gps.h
@@ -74,8 +76,18 @@ public final class GnssNavigationMessage implements Parcelable {
public static final int TYPE_GAL_I = 0x0601;
/** Galileo F/NAV message contained in the structure. */
public static final int TYPE_GAL_F = 0x0602;
- /** IRNSS L5 C/A message contained in the structure. */
+ /**
+ * NavIC L5 C/A message contained in the structure.
+ * @deprecated Use {@link #TYPE_IRN_L5} instead.
+ */
+ @Deprecated
public static final int TYPE_IRN_L5CA = 0x0701;
+ /** NavIC L5 message contained in the structure. */
+ @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
+ public static final int TYPE_IRN_L5 = 0x0702;
+ /** NavIC L1 message contained in the structure. */
+ @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
+ public static final int TYPE_IRN_L1 = 0x0703;
/**
* The status of the GNSS Navigation Message
@@ -254,8 +266,15 @@ public final class GnssNavigationMessage implements Parcelable {
case TYPE_GAL_F:
return "Galileo F";
case TYPE_IRN_L5CA:
- return "IRNSS L5 C/A";
+ return "NavIC L5 C/A";
default:
+ if (Flags.gnssApiNavicL1()) {
+ if (mType == TYPE_IRN_L5) {
+ return "NavIC L5";
+ } else if (mType == TYPE_IRN_L1) {
+ return "NavIC L1";
+ }
+ }
return "<Invalid:" + mType + ">";
}
}
@@ -303,9 +322,12 @@ public final class GnssNavigationMessage implements Parcelable {
* navigation message, in the range of 1-25 (Subframe 1, 2, 3 does not contain a 'frame id' and
* this value can be set to -1.)</li>
* <li> For Beidou CNAV1 this refers to the page type number in the range of 1-63.</li>
- * <li> For IRNSS L5 C/A subframe 3 and 4, this value corresponds to the Message Id of the
+ * <li> For NavIC L5 subframe 3 and 4, this value corresponds to the Message Id of the
* navigation message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type
* id and this value can be set to -1.)</li>
+ * <li> For NavIC L1 subframe 3, this value corresponds to the Message Id of the navigation
+ * message, in the range of 1-63. (Subframe 1 and 2 does not contain a message type id and this
+ * value can be set to -1.)</li>
* </ul>
*/
@IntRange(from = -1, to = 120)
@@ -339,8 +361,10 @@ public final class GnssNavigationMessage implements Parcelable {
* navigation message, in the range of 1-3.</li>
* <li> For Beidou CNAV2, the submessage id corresponds to the message type, in the range
* 1-63.</li>
- * <li> For IRNSS L5 C/A, the submessage id corresponds to the subframe number of the
- * navigation message, in the range of 1-4.</li>
+ * <li> For NavIC L5, the submessage id corresponds to the subframe number of the navigation
+ * message, in the range of 1-4.</li>
+ * <li> For NavIC L1, the submessage id corresponds to the subframe number of the navigation
+ * message, in the range of 1-3.</li>
* </ul>
*/
@IntRange(from = 1)
@@ -363,7 +387,7 @@ public final class GnssNavigationMessage implements Parcelable {
* <p>The bytes (or words) specified using big endian format (MSB first).
*
* <ul>
- * <li>For GPS L1 C/A, IRNSS L5 C/A, Beidou D1 &amp; Beidou D2, each subframe contains 10
+ * <li>For GPS L1 C/A, NavIC L5, Beidou D1 &amp; Beidou D2, each subframe contains 10
* 30-bit words. Each word (30 bits) should be fit into the last 30 bits in a 4-byte word (skip
* B31 and B32), with MSB first, for a total of 40 bytes, covering a time period of 6, 6, and
* 0.6 seconds, respectively.</li>
@@ -383,6 +407,9 @@ public final class GnssNavigationMessage implements Parcelable {
* 75 bytes. subframe #3 consists of 264 data bits that should be fit into 33 bytes.</li>
* <li>For Beidou CNAV2, each subframe consists of 288 data bits, that should be fit into 36
* bytes.</li>
+ * <li> For NavIC L1, subframe #1 consists of 9 data bits that should be fit into 2 bytes (skip
+ * B10-B16). subframe #2 consists of 600 bits that should be fit into 75 bytes. subframe #3
+ * consists of 274 data bits that should be fit into 35 bytes (skip B275-B280).</li>
* </ul>
*/
@NonNull
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
new file mode 100644
index 000000000000..c471a2749617
--- /dev/null
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -0,0 +1,8 @@
+package: "android.location.flags"
+
+flag {
+ name: "gnss_api_navic_l1"
+ namespace: "location"
+ description: "Flag for GNSS API for NavIC L1"
+ bug: "302199306"
+} \ No newline at end of file
diff --git a/media/Android.bp b/media/Android.bp
index f69dd3cc3206..349340804f1e 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -23,6 +23,10 @@ aidl_interface {
name: "soundtrigger_middleware-aidl",
unstable: true,
local_include_dir: "aidl",
+ defaults: [
+ "latest_android_media_audio_common_types_import_interface",
+ "latest_android_media_soundtrigger_types_import_interface",
+ ],
backend: {
java: {
sdk_version: "module_current",
@@ -32,8 +36,6 @@ aidl_interface {
"aidl/android/media/soundtrigger_middleware/*.aidl",
],
imports: [
- "android.media.audio.common.types-V2",
- "android.media.soundtrigger.types-V1",
"media_permission-aidl",
],
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 91fa873078fc..cccf6f1caca7 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -18,6 +18,9 @@ package android.media;
import static android.media.MediaRouter2Utils.toUniqueId;
+import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -141,6 +144,8 @@ public final class MediaRoute2Info implements Parcelable {
TYPE_WIRED_HEADPHONES,
TYPE_BLUETOOTH_A2DP,
TYPE_HDMI,
+ TYPE_HDMI_ARC,
+ TYPE_HDMI_EARC,
TYPE_USB_DEVICE,
TYPE_USB_ACCESSORY,
TYPE_DOCK,
@@ -206,6 +211,22 @@ public final class MediaRoute2Info implements Parcelable {
public static final int TYPE_HDMI = AudioDeviceInfo.TYPE_HDMI;
/**
+ * Indicates the route is an Audio Return Channel of an HDMI connection.
+ *
+ * @see #getType
+ */
+ @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+ public static final int TYPE_HDMI_ARC = AudioDeviceInfo.TYPE_HDMI_ARC;
+
+ /**
+ * Indicates the route is an Enhanced Audio Return Channel of an HDMI connection.
+ *
+ * @see #getType
+ */
+ @FlaggedApi(FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER)
+ public static final int TYPE_HDMI_EARC = AudioDeviceInfo.TYPE_HDMI_EARC;
+
+ /**
* Indicates the route is a USB audio device.
*
* @see #getType
@@ -907,6 +928,10 @@ public final class MediaRoute2Info implements Parcelable {
return "BLUETOOTH_A2DP";
case TYPE_HDMI:
return "HDMI";
+ case TYPE_HDMI_ARC:
+ return "HDMI_ARC";
+ case TYPE_HDMI_EARC:
+ return "HDMI_EARC";
case TYPE_DOCK:
return "DOCK";
case TYPE_USB_DEVICE:
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 159427bc2796..f68eb3d4a34a 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -52,6 +52,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -310,8 +311,11 @@ public final class MediaRouter2 {
IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
+ mSystemController =
+ new SystemRoutingController(
+ ProxyMediaRouter2Impl.getSystemSessionInfoImpl(
+ mMediaRouterService, clientPackageName));
mImpl = new ProxyMediaRouter2Impl(context, clientPackageName);
- mSystemController = new SystemRoutingController(mImpl.getSystemSessionInfo());
}
/**
@@ -380,9 +384,9 @@ public final class MediaRouter2 {
* @see #setRouteListingPreference(RouteListingPreference)
*/
@FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void registerRouteListingPreferenceCallback(
+ public void registerRouteListingPreferenceUpdatedCallback(
@NonNull @CallbackExecutor Executor executor,
- @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeListingPreferenceCallback, "callback must not be null");
@@ -395,15 +399,20 @@ public final class MediaRouter2 {
/**
* Unregisters the given callback to not receive {@link RouteListingPreference} change events.
+ *
+ * @see #registerRouteListingPreferenceUpdatedCallback(Executor, Consumer)
*/
@FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void unregisterRouteListingPreferenceCallback(
- @NonNull RouteListingPreferenceCallback callback) {
+ public void unregisterRouteListingPreferenceUpdatedCallback(
+ @NonNull Consumer<RouteListingPreference> callback) {
Objects.requireNonNull(callback, "callback must not be null");
if (!mListingPreferenceCallbackRecords.remove(
new RouteListingPreferenceCallbackRecord(/* executor */ null, callback))) {
- Log.w(TAG, "unregisterRouteListingPreferenceCallback: Ignoring an unknown callback");
+ Log.w(
+ TAG,
+ "unregisterRouteListingPreferenceUpdatedCallback: Ignoring an unknown"
+ + " callback");
}
}
@@ -1116,9 +1125,7 @@ public final class MediaRouter2 {
private void notifyRouteListingPreferenceUpdated(@Nullable RouteListingPreference preference) {
for (RouteListingPreferenceCallbackRecord record : mListingPreferenceCallbackRecords) {
record.mExecutor.execute(
- () ->
- record.mRouteListingPreferenceCallback.onRouteListingPreferenceChanged(
- preference));
+ () -> record.mRouteListingPreferenceCallback.accept(preference));
}
}
@@ -1205,22 +1212,6 @@ public final class MediaRouter2 {
public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
}
- /** Callback for receiving events related to {@link RouteListingPreference}. */
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public abstract static class RouteListingPreferenceCallback {
-
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public RouteListingPreferenceCallback() {}
-
- /**
- * Called when the {@link RouteListingPreference} changes.
- *
- * @see #getRouteListingPreference
- */
- @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
- public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
- }
-
/** Callback for receiving events on media transfer. */
public abstract static class TransferCallback {
/**
@@ -1771,11 +1762,11 @@ public final class MediaRouter2 {
private static final class RouteListingPreferenceCallbackRecord {
public final Executor mExecutor;
- public final RouteListingPreferenceCallback mRouteListingPreferenceCallback;
+ public final Consumer<RouteListingPreference> mRouteListingPreferenceCallback;
/* package */ RouteListingPreferenceCallbackRecord(
@NonNull Executor executor,
- @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
+ @NonNull Consumer<RouteListingPreference> routeListingPreferenceCallback) {
mExecutor = executor;
mRouteListingPreferenceCallback = routeListingPreferenceCallback;
}
@@ -2057,13 +2048,7 @@ public final class MediaRouter2 {
@Override
public RoutingSessionInfo getSystemSessionInfo() {
- RoutingSessionInfo result;
- try {
- result = mMediaRouterService.getSystemSessionInfoForPackage(mClientPackageName);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- return ensureClientPackageNameForSystemSession(result);
+ return getSystemSessionInfoImpl(mMediaRouterService, mClientPackageName);
}
/**
@@ -2428,6 +2413,23 @@ public final class MediaRouter2 {
}
/**
+ * Retrieves the system session info for the given package.
+ *
+ * <p>The returned routing session is guaranteed to have a non-null {@link
+ * RoutingSessionInfo#getClientPackageName() client package name}.
+ *
+ * <p>Extracted into a static method to allow calling this from the constructor.
+ */
+ /* package */ static RoutingSessionInfo getSystemSessionInfoImpl(
+ @NonNull IMediaRouterService service, @NonNull String clientPackageName) {
+ try {
+ return service.getSystemSessionInfoForPackage(clientPackageName);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client
* package name} to {@link #mClientPackageName} if empty and returns the session.
*
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 31e65eb13926..10c880d8ab06 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -176,4 +176,47 @@ interface IMediaProjectionManager {
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
+
+ /**
+ * Notifies system server that the permission request was initiated.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ * @param sessionCreationSource Only set if the state is MEDIA_PROJECTION_STATE_INITIATED.
+ * Indicates the entry point for requesting the permission. Must be
+ * a valid state defined
+ * in the SessionCreationSource enum.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource);
+
+ /**
+ * Notifies system server that the permission request was displayed.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyPermissionRequestDisplayed(int hostUid);
+
+ /**
+ * Notifies system server that the app selector was displayed.
+ *
+ * <p>Only used for emitting atoms.
+ *
+ * @param hostUid The uid of the process requesting consent to capture, may be an app or
+ * SystemUI.
+ */
+ @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.MANAGE_MEDIA_PROJECTION)")
+ oneway void notifyAppSelectorDisplayed(int hostUid);
}
diff --git a/nfc-extras/OWNERS b/nfc-extras/OWNERS
new file mode 100644
index 000000000000..35e9713f5715
--- /dev/null
+++ b/nfc-extras/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48448
+include platform/packages/apps/Nfc:/OWNERS
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
index ffed80479662..fe8386e523b2 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
@@ -16,6 +16,8 @@
package com.android.nfc_extras;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.HashMap;
import android.content.Context;
@@ -30,6 +32,8 @@ import android.util.Log;
*
* There is a 1-1 relationship between an {@link NfcAdapterExtras} object and
* a {@link NfcAdapter} object.
+ *
+ * TODO(b/303286040): Deprecate this API surface since this is no longer supported (see ag/443092)
*/
public final class NfcAdapterExtras {
private static final String TAG = "NfcAdapterExtras";
@@ -72,15 +76,40 @@ public final class NfcAdapterExtras {
private final NfcAdapter mAdapter;
final String mPackageName;
+ private static INfcAdapterExtras
+ getNfcAdapterExtrasInterfaceFromNfcAdapter(NfcAdapter adapter) {
+ try {
+ Method method = NfcAdapter.class.getDeclaredMethod("getNfcAdapterExtrasInterface");
+ method.setAccessible(true);
+ return (INfcAdapterExtras) method.invoke(adapter);
+ } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+ | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+ Log.e(TAG, "Unable to get context from NfcAdapter");
+ }
+ return null;
+ }
+
/** get service handles */
private static void initService(NfcAdapter adapter) {
- final INfcAdapterExtras service = adapter.getNfcAdapterExtrasInterface();
+ final INfcAdapterExtras service = getNfcAdapterExtrasInterfaceFromNfcAdapter(adapter);
if (service != null) {
// Leave stale rather than receive a null value.
sService = service;
}
}
+ private static Context getContextFromNfcAdapter(NfcAdapter adapter) {
+ try {
+ Method method = NfcAdapter.class.getDeclaredMethod("getContext");
+ method.setAccessible(true);
+ return (Context) method.invoke(adapter);
+ } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+ | IllegalAccessException | IllegalAccessError | InvocationTargetException e) {
+ Log.e(TAG, "Unable to get context from NfcAdapter");
+ }
+ return null;
+ }
+
/**
* Get the {@link NfcAdapterExtras} for the given {@link NfcAdapter}.
*
@@ -91,7 +120,7 @@ public final class NfcAdapterExtras {
* @return the {@link NfcAdapterExtras} object for the given {@link NfcAdapter}
*/
public static NfcAdapterExtras get(NfcAdapter adapter) {
- Context context = adapter.getContext();
+ Context context = getContextFromNfcAdapter(adapter);
if (context == null) {
throw new UnsupportedOperationException(
"You must pass a context to your NfcAdapter to use the NFC extras APIs");
@@ -112,7 +141,7 @@ public final class NfcAdapterExtras {
private NfcAdapterExtras(NfcAdapter adapter) {
mAdapter = adapter;
- mPackageName = adapter.getContext().getPackageName();
+ mPackageName = getContextFromNfcAdapter(adapter).getPackageName();
mEmbeddedEe = new NfcExecutionEnvironment(this);
mRouteOnWhenScreenOn = new CardEmulationRoute(CardEmulationRoute.ROUTE_ON_WHEN_SCREEN_ON,
mEmbeddedEe);
@@ -156,12 +185,24 @@ public final class NfcAdapterExtras {
}
}
+ private static void attemptDeadServiceRecoveryOnNfcAdapter(NfcAdapter adapter, Exception e) {
+ try {
+ Method method = NfcAdapter.class.getDeclaredMethod(
+ "attemptDeadServiceRecovery", Exception.class);
+ method.setAccessible(true);
+ method.invoke(adapter, e);
+ } catch (SecurityException | NoSuchMethodException | IllegalArgumentException
+ | IllegalAccessException | IllegalAccessError | InvocationTargetException ex) {
+ Log.e(TAG, "Unable to attempt dead service recovery on NfcAdapter");
+ }
+ }
+
/**
* NFC service dead - attempt best effort recovery
*/
void attemptDeadServiceRecovery(Exception e) {
Log.e(TAG, "NFC Adapter Extras dead - attempting to recover");
- mAdapter.attemptDeadServiceRecovery(e);
+ attemptDeadServiceRecoveryOnNfcAdapter(mAdapter, e);
initService(mAdapter);
}
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 30d1773cfb38..4e4894c8e35d 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -27,7 +27,7 @@
<string name="passkey_creation_intro_title" msgid="4251037543787718844">"Geçiş anahtarlarıyla daha yüksek güvenlik"</string>
<string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Geçiş anahtarı kullandığınızda karmaşık şifreler oluşturmanız veya bunları hatırlamanız gerekmez"</string>
<string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Geçiş anahtarları; parmak iziniz, yüzünüz veya ekran kilidinizi kullanarak oluşturduğunuz şifrelenmiş dijital anahtarlardır"</string>
- <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için şifre anahtarları bir şifre yöneticisine kaydedilir"</string>
+ <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Diğer cihazlarda oturum açabilmeniz için geçiş anahtarları bir şifre yöneticisine kaydedilir"</string>
<string name="more_about_passkeys_title" msgid="7797903098728837795">"Geçiş anahtarları hakkında daha fazla bilgi"</string>
<string name="passwordless_technology_title" msgid="2497513482056606668">"Şifresiz teknoloji"</string>
<string name="passwordless_technology_detail" msgid="6853928846532955882">"Geçiş anahtarları, şifre kullanmadan oturum açmanıza olanak tanır. Kimliğinizi doğrulayıp geçiş anahtarı oluşturmak için parmak iziniz, yüz tanıma özelliği, PIN veya kaydırma deseni kullanmanız yeterlidir."</string>
@@ -39,10 +39,10 @@
<string name="seamless_transition_detail" msgid="4475509237171739843">"Şifresiz bir geleceğe doğru ilerlerken şifreler, geçiş anahtarlarıyla birlikte kullanılmaya devam edecektir."</string>
<string name="choose_provider_title" msgid="8870795677024868108">"<xliff:g id="CREATETYPES">%1$s</xliff:g> kaydedileceği yeri seçin"</string>
<string name="choose_provider_body" msgid="4967074531845147434">"Bilgilerinizi kaydedip bir dahaki sefere daha hızlı oturum açmak için bir şifre yöneticisi seçin"</string>
- <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre anahtarı oluşturulsun mu?"</string>
+ <string name="choose_create_option_passkey_title" msgid="5220979185879006862">"<xliff:g id="APPNAME">%1$s</xliff:g> için geçiş anahtarı oluşturulsun mu?"</string>
<string name="choose_create_option_password_title" msgid="7097275038523578687">"<xliff:g id="APPNAME">%1$s</xliff:g> için şifre kaydedilsin mi?"</string>
<string name="choose_create_option_sign_in_title" msgid="4124872317613421249">"<xliff:g id="APPNAME">%1$s</xliff:g> için oturum açma bilgileri kaydedilsin mi?"</string>
- <string name="passkey" msgid="632353688396759522">"Şifre anahtarı"</string>
+ <string name="passkey" msgid="632353688396759522">"Geçiş anahtarı"</string>
<string name="password" msgid="6738570945182936667">"Şifre"</string>
<string name="passkeys" msgid="5733880786866559847">"Geçiş anahtarlarınızın"</string>
<string name="passwords" msgid="5419394230391253816">"şifreler"</string>
@@ -61,14 +61,14 @@
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> geçiş anahtarı"</string>
<string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> kimlik bilgileri"</string>
- <string name="passkey_before_subtitle" msgid="2448119456208647444">"Şifre anahtarı"</string>
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Geçiş anahtarı"</string>
<string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string>
<string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
<string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
<string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Önceki sayfaya geri dön"</string>
<string name="accessibility_close_button" msgid="1163435587545377687">"Kapat"</string>
<string name="accessibility_snackbar_dismiss" msgid="3456598374801836120">"Kapat"</string>
- <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifre anahtarınız kullanılsın mı?"</string>
+ <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı geçiş anahtarınız kullanılsın mı?"</string>
<string name="get_dialog_title_use_password_for" msgid="625828023234318484">"<xliff:g id="APP_NAME">%1$s</xliff:g> için kayıtlı şifreniz kullanılsın mı?"</string>
<string name="get_dialog_title_use_sign_in_for" msgid="790049858275131785">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma bilgileriniz kullanılsın mı?"</string>
<string name="get_dialog_title_unlock_options_for" msgid="7605568190597632433">"<xliff:g id="APP_NAME">%1$s</xliff:g> için oturum açma seçeneklerine izin verilsin mi?"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 222b8654d619..495abe660f4d 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -53,7 +53,7 @@
<string name="save_password_on_other_device_title" msgid="5829084591948321207">"在其他设备上保存密码?"</string>
<string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"在其他设备上保存登录凭据?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
- <string name="use_provider_for_all_description" msgid="1998772715863958997">"此 <xliff:g id="USERNAME">%1$s</xliff:g> 密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
+ <string name="use_provider_for_all_description" msgid="1998772715863958997">"<xliff:g id="USERNAME">%1$s</xliff:g> 的这个密码管理工具将会存储您的密码和通行密钥,帮助您轻松登录"</string>
<string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string>
<string name="settings" msgid="6536394145760913145">"设置"</string>
<string name="use_once" msgid="9027366575315399714">"使用一次"</string>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 9355517d8bf9..d35dcb54c36d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -43,6 +43,9 @@ import org.json.JSONException
import android.widget.inline.InlinePresentationSpec
import androidx.autofill.inline.v1.InlineSuggestionUi
import com.android.credentialmanager.GetFlowUtils
+import com.android.credentialmanager.getflow.CredentialEntryInfo
+import com.android.credentialmanager.getflow.ProviderDisplayInfo
+import com.android.credentialmanager.getflow.toProviderDisplayInfo
import org.json.JSONObject
import java.util.concurrent.Executors
@@ -114,10 +117,14 @@ class CredentialAutofillService : AutofillService() {
val providerList = GetFlowUtils.toProviderList(
getCredResponse.candidateProviderDataList,
this@CredentialAutofillService)
+ if (providerList.isEmpty()) {
+ return null
+ }
var totalEntryCount = 0
providerList.forEach { provider ->
totalEntryCount += provider.credentialEntryList.size
}
+ val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerList)
val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest
val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
@@ -129,15 +136,30 @@ class CredentialAutofillService : AutofillService() {
var i = 0
val fillResponseBuilder = FillResponse.Builder()
var emptyFillResponse = true
- providerList.forEach {provider ->
- // TODO(b/299321128): Before iterating the list, sort the list so that
- // the relevant entries don't get truncated
- provider.credentialEntryList.forEach entryLoop@ {entry ->
- val autofillId: AutofillId? = entry.fillInIntent?.getParcelableExtra(
- CredentialProviderService.EXTRA_AUTOFILL_ID,
- AutofillId::class.java)
- val pendingIntent = entry.pendingIntent
+
+ providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@ {
+ val primaryEntry = it.sortedCredentialEntryList.first()
+ // In regular CredMan bottomsheet, only one primary entry per username is displayed.
+ // But since the credential requests from different fields are allocated into a single
+ // request for autofill, there will be duplicate primary entries, especially for
+ // username/pw autofill fields. These primary entries will be the same entries except
+ // their autofillIds will point to different autofill fields. Process all primary
+ // fields.
+ // TODO(b/307435163): Merge credential options
+ it.sortedCredentialEntryList.forEach entryLoop@ { credentialEntry ->
+ if (!isSameCredentialEntry(primaryEntry, credentialEntry)) {
+ // Encountering different credential entry means all the duplicate primary
+ // entries have been processed.
+ return@usernameLoop
+ }
+ val autofillId: AutofillId? = credentialEntry
+ .fillInIntent
+ ?.getParcelableExtra(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ AutofillId::class.java)
+ val pendingIntent = credentialEntry.pendingIntent
if (autofillId == null || pendingIntent == null) {
+ Log.e(TAG, "AutofillId or pendingIntent was missing from the entry.")
return@entryLoop
}
var inlinePresentation: InlinePresentation? = null
@@ -151,7 +173,7 @@ class CredentialAutofillService : AutofillService() {
}
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
- .setTitle(entry.userName)
+ .setTitle(credentialEntry.userName)
inlinePresentation = InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ false)
}
@@ -169,8 +191,8 @@ class CredentialAutofillService : AutofillService() {
Field.Builder().setPresentations(
presentationBuilder.build())
.build())
- .setAuthentication(entry.pendingIntent.intentSender)
- .setAuthenticationExtras(entry.fillInIntent.extras)
+ .setAuthentication(pendingIntent.intentSender)
+ .setAuthenticationExtras(credentialEntry.fillInIntent.extras)
.build())
emptyFillResponse = false
}
@@ -292,4 +314,15 @@ class CredentialAutofillService : AutofillService() {
}
return result
}
+
+ private fun isSameCredentialEntry(
+ info1: CredentialEntryInfo,
+ info2: CredentialEntryInfo
+ ): Boolean {
+ return info1.providerId == info2.providerId &&
+ info1.lastUsedTimeMillis == info2.lastUsedTimeMillis &&
+ info1.credentialType == info2.credentialType &&
+ info1.displayName == info2.displayName &&
+ info1.userName == info2.userName
+ }
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 716f47450ae9..447a9d2aaa8d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -203,9 +203,14 @@ enum class GetScreenState {
UNLOCKED_AUTH_ENTRIES_ONLY,
}
-// IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
-// entry exists
-private fun toProviderDisplayInfo(
+
+/**
+ * IMPORTANT: new invocation should be mindful that this method will throw if more than 1 remote
+ * entry exists
+ *
+ * @hide
+ */
+fun toProviderDisplayInfo(
providerInfoList: List<ProviderInfo>
): ProviderDisplayInfo {
val userNameToCredentialEntryMap = mutableMapOf<String, MutableList<CredentialEntryInfo>>()
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 897585729596..47ce58735048 100644
--- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -83,7 +83,7 @@
android:id="@android:id/progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="4dp"
android:layout_marginTop="4dp"
android:max="100"
android:visibility="gone"/>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index f1e028b405db..6979825c9a20 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -25,6 +25,7 @@
<item name="editTextPreferenceStyle">@style/SettingsEditTextPreference.SettingsLib</item>
<item name="dropdownPreferenceStyle">@style/SettingsDropdownPreference.SettingsLib</item>
<item name="switchPreferenceStyle">@style/SettingsSwitchPreference.SettingsLib</item>
+ <item name="switchPreferenceCompatStyle">@style/SettingsSwitchPreferenceCompat.SettingsLib</item>
<item name="seekBarPreferenceStyle">@style/SettingsSeekbarPreference.SettingsLib</item>
<item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
@@ -67,6 +68,11 @@
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
</style>
+ <style name="SettingsSwitchPreferenceCompat.SettingsLib" parent="@style/Preference.SwitchPreferenceCompat.Material">
+ <item name="layout">@layout/settingslib_preference</item>
+ <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
+ </style>
+
<style name="SettingsSeekbarPreference.SettingsLib" parent="@style/Preference.SeekBarPreference.Material">
<item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
</style>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index c4b6047e14fe..f44b16104f99 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -34,12 +34,17 @@
</style>
<style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch">
- <item name="android:switchMinWidth">52dp</item>
<item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
<item name="android:track">@drawable/settingslib_switch_track</item>
<item name="android:thumb">@drawable/settingslib_switch_thumb</item>
</style>
+ <style name="SwitchCompat.SettingsLib" parent="@style/Widget.AppCompat.CompoundButton.Switch">
+ <item name="android:minHeight">@dimen/settingslib_preferred_minimum_touch_target</item>
+ <item name="track">@drawable/settingslib_switch_track</item>
+ <item name="android:thumb">@drawable/settingslib_switch_thumb</item>
+ </style>
+
<style name="HorizontalProgressBar.SettingsLib"
parent="android:style/Widget.Material.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/settingslib_progress_horizontal</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 69c122c9992e..698f21d8d8a6 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -25,6 +25,7 @@
<item name="android:listPreferredItemPaddingRight">16dp</item>
<item name="preferenceTheme">@style/PreferenceTheme.SettingsLib</item>
<item name="android:switchStyle">@style/Switch.SettingsLib</item>
+ <item name="switchStyle">@style/SwitchCompat.SettingsLib</item>
<item name="android:progressBarStyleHorizontal">@style/HorizontalProgressBar.SettingsLib</item>
</style>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
index 90c44b549df9..330755c0b419 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryHighlight.kt
@@ -30,11 +30,11 @@ 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) {
+internal fun EntryHighlight(content: @Composable () -> Unit) {
val entryData = LocalEntryDataProvider.current
val entryIsHighlighted = rememberSaveable { entryData.isHighlighted }
var localHighlighted by rememberSaveable { mutableStateOf(false) }
@@ -45,15 +45,16 @@ internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
val backgroundColor by animateColorAsState(
targetValue = when {
localHighlighted -> MaterialTheme.colorScheme.surfaceVariant
- else -> SettingsTheme.colorScheme.background
+ else -> Color.Transparent
},
animationSpec = repeatable(
iterations = 3,
animation = tween(durationMillis = 500),
repeatMode = RepeatMode.Restart
- )
+ ),
+ label = "BackgroundColorAnimation",
)
Box(modifier = Modifier.background(color = backgroundColor)) {
- UiLayoutFn()
+ content()
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index eaeda3c67545..009407a91812 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -38,6 +38,7 @@ java_defaults {
static_libs: [
"androidx.compose.runtime_runtime",
"SpaPrivilegedLib",
+ "android.content.pm.flags-aconfig-java",
],
kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index a428142f4cc8..d95dd8c9ab13 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -17,6 +17,8 @@
package com.android.settingslib.spaprivileged.model.app
import android.content.Context
+import android.content.pm.FeatureFlags
+import android.content.pm.FeatureFlagsImpl
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
@@ -65,10 +67,15 @@ object AppListRepositoryUtil {
AppListRepositoryImpl(context).getSystemPackageNamesBlocking(userId)
}
-class AppListRepositoryImpl(private val context: Context) : AppListRepository {
+class AppListRepositoryImpl(
+ private val context: Context,
+ private val featureFlags: FeatureFlags
+) : AppListRepository {
private val packageManager = context.packageManager
private val userManager = context.userManager
+ constructor(context: Context) : this(context, FeatureFlagsImpl())
+
override suspend fun loadApps(
userId: Int,
loadInstantApps: Boolean,
@@ -98,9 +105,13 @@ class AppListRepositoryImpl(private val context: Context) : AppListRepository {
userId: Int,
matchAnyUserForAdmin: Boolean,
): List<ApplicationInfo> {
+ val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+ val archivedPackagesFlag: Long = if (featureFlags.archiving())
+ PackageManager.MATCH_ARCHIVED_PACKAGES else 0L
val regularFlags = ApplicationInfoFlags.of(
- (PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+ disabledComponentsFlag or
+ archivedPackagesFlag
)
return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index 517f67e3c44b..840bca8b14ec 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -47,6 +47,8 @@ import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
+import android.content.pm.FakeFeatureFlagsImpl
+import android.content.pm.Flags
@RunWith(AndroidJUnit4::class)
class AppListRepositoryTest {
@@ -268,6 +270,40 @@ class AppListRepositoryTest {
}
@Test
+ fun loadApps_archivedAppsEnabled() = runTest {
+ val fakeFlags = FakeFeatureFlagsImpl()
+ fakeFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+ mockInstalledApplications(listOf(NORMAL_APP, ARCHIVED_APP), ADMIN_USER_ID)
+ val repository = AppListRepositoryImpl(context, fakeFlags)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP, ARCHIVED_APP)
+ argumentCaptor<ApplicationInfoFlags> {
+ verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
+ assertThat(firstValue.value).isEqualTo(
+ (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() or
+ PackageManager.MATCH_ARCHIVED_PACKAGES
+ )
+ }
+ }
+
+ @Test
+ fun loadApps_archivedAppsDisabled() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ argumentCaptor<ApplicationInfoFlags> {
+ verify(packageManager).getInstalledApplicationsAsUser(capture(), eq(ADMIN_USER_ID))
+ assertThat(firstValue.value).isEqualTo(
+ PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ )
+ }
+ }
+
+ @Test
fun showSystemPredicate_showSystem() = runTest {
val app = SYSTEM_APP
@@ -391,6 +427,12 @@ class AppListRepositoryTest {
flags = ApplicationInfo.FLAG_SYSTEM
}
+ val ARCHIVED_APP = ApplicationInfo().apply {
+ packageName = "archived.app"
+ flags = ApplicationInfo.FLAG_SYSTEM
+ isArchived = true
+ }
+
fun resolveInfoOf(packageName: String) = ResolveInfo().apply {
activityInfo = ActivityInfo().apply {
this.packageName = packageName
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index afdb92b1bbab..9d986f4f306c 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Kies profiel"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoonlik"</string>
<string name="category_work" msgid="4014193632325996115">"Werk"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privaat"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaaropsies"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktiveer ontwikkelaaropsies"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index ea8491b2138c..b4887b9e56e3 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"መገለጫ ይምረጡ"</string>
<string name="category_personal" msgid="6236798763159385225">"የግል"</string>
<string name="category_work" msgid="4014193632325996115">"ሥራ"</string>
+ <string name="category_private" msgid="4244892185452788977">"የግል"</string>
<string name="category_clone" msgid="1554511758987195974">"አባዛ"</string>
<string name="development_settings_title" msgid="140296922921597393">"የገንቢዎች አማራጮች"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"የገንቢዎች አማራጮችን አንቃ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index c29e6911396b..b8f92a39dca9 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"اختيار ملف شخصي"</string>
<string name="category_personal" msgid="6236798763159385225">"التطبيقات الشخصية"</string>
<string name="category_work" msgid="4014193632325996115">"تطبيقات العمل"</string>
+ <string name="category_private" msgid="4244892185452788977">"ملف شخصي"</string>
<string name="category_clone" msgid="1554511758987195974">"استنساخ"</string>
<string name="development_settings_title" msgid="140296922921597393">"خيارات المطورين"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"تفعيل خيارات المطورين"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index 8eff4f6eba44..86b29c60d4ed 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"প্ৰ’ফাইল বাছনি কৰক"</string>
<string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string>
<string name="category_work" msgid="4014193632325996115">"কৰ্মস্থান-সম্পৰ্কীয়"</string>
+ <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string>
<string name="category_clone" msgid="1554511758987195974">"ক্ল’ন"</string>
<string name="development_settings_title" msgid="140296922921597393">"বিকাশকৰ্তাৰ বিকল্পসমূহ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"বিকাশকৰ্তা বিষয়ক বিকল্পসমূহ সক্ষম কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index dfd1b7b83204..6a08a2523e2b 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
<string name="category_personal" msgid="6236798763159385225">"Şəxsi"</string>
<string name="category_work" msgid="4014193632325996115">"İş"</string>
+ <string name="category_private" msgid="4244892185452788977">"Şəxsi"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonlayın"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer seçimləri"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Developer variantlarını aktiv edin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 528e96e4c747..ad829b9425cf 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Izaberite profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Lično"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonirano"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 52614b7df160..7cd748c2a782 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Выбраць профіль"</string>
<string name="category_personal" msgid="6236798763159385225">"Асабісты"</string>
<string name="category_work" msgid="4014193632325996115">"Працоўны"</string>
+ <string name="category_private" msgid="4244892185452788977">"Прыватныя"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Параметры распрацоўшчыка"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Уключыць параметры распрацоўшчыка"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index b28ae6742f68..93fe481df43a 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Избор на потребителски профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лични"</string>
<string name="category_work" msgid="4014193632325996115">"Служебни"</string>
+ <string name="category_private" msgid="4244892185452788977">"Частен"</string>
<string name="category_clone" msgid="1554511758987195974">"Клониране"</string>
<string name="development_settings_title" msgid="140296922921597393">"Опции за програмисти"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Активиране на опциите за програмисти"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index c91f14ca2fad..efdf304a00ca 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"প্রোফাইল বেছে নিন"</string>
<string name="category_personal" msgid="6236798763159385225">"ব্যক্তিগত"</string>
<string name="category_work" msgid="4014193632325996115">"অফিস"</string>
+ <string name="category_private" msgid="4244892185452788977">"ব্যক্তিগত"</string>
<string name="category_clone" msgid="1554511758987195974">"ক্লোন"</string>
<string name="development_settings_title" msgid="140296922921597393">"ডেভেলপার বিকল্প"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ডেভেলপার বিকল্প সক্ষম করুন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index aa1073d74d40..5d21dea9fc18 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Odaberite profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Lično"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonirajte"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za programere"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index ab6393a7d407..ea4a2fb86a45 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Tria un perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Treball"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Clona"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcions per a desenvolupadors"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activa les opcions per a desenvolupadors"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index e7d524317bae..648e8dd63a4d 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Vyberte profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobní"</string>
<string name="category_work" msgid="4014193632325996115">"Pracovní"</string>
+ <string name="category_private" msgid="4244892185452788977">"Soukromé"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pro vývojáře"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivovat možnosti pro vývojáře"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 1612ac030205..500bfc35121c 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Vælg profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
<string name="category_work" msgid="4014193632325996115">"Arbejde"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Indstillinger for udviklere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivér indstillinger for udviklere"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 73a44f140cbf..df392f3dd2a2 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -216,6 +216,8 @@
<string name="choose_profile" msgid="343803890897657450">"Profil auswählen"</string>
<string name="category_personal" msgid="6236798763159385225">"Privat"</string>
<string name="category_work" msgid="4014193632325996115">"Geschäftlich"</string>
+ <!-- no translation found for category_private (4244892185452788977) -->
+ <skip />
<string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
<string name="development_settings_title" msgid="140296922921597393">"Entwickleroptionen"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Entwickleroptionen aktivieren"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 7dfd679ffacf..a87c0d7d84b6 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Επιλογή προφίλ"</string>
<string name="category_personal" msgid="6236798763159385225">"Προσωπικό"</string>
<string name="category_work" msgid="4014193632325996115">"Εργασίας"</string>
+ <string name="category_private" msgid="4244892185452788977">"Ιδιωτικό"</string>
<string name="category_clone" msgid="1554511758987195974">"Κλωνοποίηση"</string>
<string name="development_settings_title" msgid="140296922921597393">"Επιλογές για προγραμματιστές"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ενεργοποίηση επιλογών για προγραμματιστές"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index aead40d12072..5193f9b966bc 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 46dd47d4a4c1..11a39b20cb9e 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index aead40d12072..5193f9b966bc 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index aead40d12072..5193f9b966bc 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Choose profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Work"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Developer options"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Enable developer options"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index a7c327f7618b..8a32195c063a 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‏‏‎‏‎‏‎‎Choose profile‎‏‎‎‏‎"</string>
<string name="category_personal" msgid="6236798763159385225">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‎‏‎Personal‎‏‎‎‏‎"</string>
<string name="category_work" msgid="4014193632325996115">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎Work‎‏‎‎‏‎"</string>
+ <string name="category_private" msgid="4244892185452788977">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‎Private‎‏‎‎‏‎"</string>
<string name="category_clone" msgid="1554511758987195974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‎Clone‎‏‎‎‏‎"</string>
<string name="development_settings_title" msgid="140296922921597393">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‎‏‎Developer options‎‏‎‎‏‎"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‎Enable developer options‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 291a68bc14fc..4276f59a281c 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Elegir perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activar opciones para programador"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index b4bc31913902..8b7b2118c412 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Seleccionar perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabajo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opciones para desarrolladores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Habilitar opciones para desarrolladores"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index e5cbaf61fbb5..9c46b6c07f28 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profiili valimine"</string>
<string name="category_personal" msgid="6236798763159385225">"Isiklik"</string>
<string name="category_work" msgid="4014193632325996115">"Töö"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privaatne"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloonimine"</string>
<string name="development_settings_title" msgid="140296922921597393">"Arendaja valikud"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Arendaja valikute lubamine"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 90d45eb44fdc..4436d52b6f0b 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Aukeratu profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Pertsonalak"</string>
<string name="category_work" msgid="4014193632325996115">"Lanekoak"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribatua"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonatu"</string>
<string name="development_settings_title" msgid="140296922921597393">"Garatzaileentzako aukerak"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Gaitu garatzaileen aukerak"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index f3f97c460780..7ae6b7ba7ccd 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"انتخاب نمایه"</string>
<string name="category_personal" msgid="6236798763159385225">"شخصی"</string>
<string name="category_work" msgid="4014193632325996115">"کاری"</string>
+ <string name="category_private" msgid="4244892185452788977">"خصوصی"</string>
<string name="category_clone" msgid="1554511758987195974">"مشابه‌سازی"</string>
<string name="development_settings_title" msgid="140296922921597393">"گزینه‌های برنامه‌نویسان"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینه‌های برنامه‌نویس"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 7f3d0f286695..6822d081d470 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Valitse profiili"</string>
<string name="category_personal" msgid="6236798763159385225">"Henkilökohtainen"</string>
<string name="category_work" msgid="4014193632325996115">"Työ"</string>
+ <string name="category_private" msgid="4244892185452788977">"Yksityinen"</string>
<string name="category_clone" msgid="1554511758987195974">"Klooni"</string>
<string name="development_settings_title" msgid="140296922921597393">"Kehittäjäasetukset"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ota kehittäjäasetukset käyttöön"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index b7c3a81b47cd..12bc78eeeb2b 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Sélectionnez un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personnel"</string>
<string name="category_work" msgid="4014193632325996115">"Professionnel"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privés"</string>
<string name="category_clone" msgid="1554511758987195974">"Cloner"</string>
<string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index a9e0c6a5ccb5..527b4737dae3 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Sélectionner un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Perso"</string>
<string name="category_work" msgid="4014193632325996115">"Pro"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privé"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 09c2969bac2d..00d7b6e5e062 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escoller perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoal"</string>
<string name="category_work" msgid="4014193632325996115">"Traballo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonar"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcións para programadores"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activar opcións para programadores"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 575c67510e89..b0819fb58c91 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"પ્રોફાઇલ પસંદ કરો"</string>
<string name="category_personal" msgid="6236798763159385225">"વ્યક્તિગત"</string>
<string name="category_work" msgid="4014193632325996115">"ઑફિસ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ખાનગી"</string>
<string name="category_clone" msgid="1554511758987195974">"ક્લોન કરો"</string>
<string name="development_settings_title" msgid="140296922921597393">"ડેવલપરના વિકલ્પો"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"વિકાસકર્તાનાં વિકલ્પો સક્ષમ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 655fac041c9b..99572f394202 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफ़ाइल चुनें"</string>
<string name="category_personal" msgid="6236798763159385225">"निजी"</string>
<string name="category_work" msgid="4014193632325996115">"वर्क"</string>
+ <string name="category_private" msgid="4244892185452788977">"निजी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन"</string>
<string name="development_settings_title" msgid="140296922921597393">"डेवलपर के लिए सेटिंग और टूल"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"डेवलपर के लिए सेटिंग और टूल चालू करें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index f0e16a47346f..735d3e9d3a25 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Odabir profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobno"</string>
<string name="category_work" msgid="4014193632325996115">"Posao"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatno"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloniranje"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcije za razvojne programere"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogući opcije za razvojne programere"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 420e0e969fd9..70b7d58122b3 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil kiválasztása"</string>
<string name="category_personal" msgid="6236798763159385225">"Személyes"</string>
<string name="category_work" msgid="4014193632325996115">"Munkahelyi"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privát"</string>
<string name="category_clone" msgid="1554511758987195974">"Klónozás"</string>
<string name="development_settings_title" msgid="140296922921597393">"Fejlesztői beállítások"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Fejlesztői beállítások engedélyezése"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 705fcf6637b2..9340a3b0f2ed 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Ընտրեք պրոֆիլ"</string>
<string name="category_personal" msgid="6236798763159385225">"Անձնական"</string>
<string name="category_work" msgid="4014193632325996115">"Աշխատանքային"</string>
+ <string name="category_private" msgid="4244892185452788977">"Անձնական"</string>
<string name="category_clone" msgid="1554511758987195974">"Կլոն"</string>
<string name="development_settings_title" msgid="140296922921597393">"Մշակողի ընտրանքներ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Միացնել մշակողի ընտրանքները"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index e33dd05264c3..8cb3dca41485 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pribadi"</string>
<string name="category_work" msgid="4014193632325996115">"Kerja"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribadi"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opsi developer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktifkan opsi developer"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index e1fb248604c2..2f8ee7e28284 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Veldu snið"</string>
<string name="category_personal" msgid="6236798763159385225">"Persónulegt"</string>
<string name="category_work" msgid="4014193632325996115">"Vinna"</string>
+ <string name="category_private" msgid="4244892185452788977">"Lokað"</string>
<string name="category_clone" msgid="1554511758987195974">"Afrit"</string>
<string name="development_settings_title" msgid="140296922921597393">"Forritunarkostir"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Virkja valkosti þróunaraðila"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index b2cfd371fe25..9fe9f304d853 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Scegli profilo"</string>
<string name="category_personal" msgid="6236798763159385225">"Personale"</string>
<string name="category_work" msgid="4014193632325996115">"Lavoro"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privato"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opzioni sviluppatore"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Attiva Opzioni sviluppatore"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 9ca74778d978..56ce9649e493 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"בחירת פרופיל"</string>
<string name="category_personal" msgid="6236798763159385225">"אישי"</string>
<string name="category_work" msgid="4014193632325996115">"עבודה"</string>
+ <string name="category_private" msgid="4244892185452788977">"פרטי"</string>
<string name="category_clone" msgid="1554511758987195974">"שכפול"</string>
<string name="development_settings_title" msgid="140296922921597393">"אפשרויות למפתחים"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"הפעלת אפשרויות למפתחים"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index dcce2b4436aa..50f57ef39217 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"プロファイルの選択"</string>
<string name="category_personal" msgid="6236798763159385225">"個人用"</string>
<string name="category_work" msgid="4014193632325996115">"仕事用"</string>
+ <string name="category_private" msgid="4244892185452788977">"非公開"</string>
<string name="category_clone" msgid="1554511758987195974">"クローン"</string>
<string name="development_settings_title" msgid="140296922921597393">"開発者向けオプション"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"開発者向けオプションの有効化"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index eb32e1ce2a8d..46e406d0291b 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"აირჩიეთ პროფილი"</string>
<string name="category_personal" msgid="6236798763159385225">"პირადი"</string>
<string name="category_work" msgid="4014193632325996115">"სამსახური"</string>
+ <string name="category_private" msgid="4244892185452788977">"პირადი"</string>
<string name="category_clone" msgid="1554511758987195974">"კლონის შექმნა"</string>
<string name="development_settings_title" msgid="140296922921597393">"პარამეტრები დეველოპერებისთვის"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"დეველოპერთა პარამეტრების ჩართვა"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 73a8efde7a97..42c8ca73d038 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профильді таңдау"</string>
<string name="category_personal" msgid="6236798763159385225">"Жеке"</string>
<string name="category_work" msgid="4014193632325996115">"Жұмыс"</string>
+ <string name="category_private" msgid="4244892185452788977">"Жеке"</string>
<string name="category_clone" msgid="1554511758987195974">"Клондау"</string>
<string name="development_settings_title" msgid="140296922921597393">"Әзірлеуші опциялары"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Әзірлеуші ​​параметрлерін қосу"</string>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 1c32e626f1b6..9503049e6619 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ជ្រើសរើស​កម្រងព័ត៌មាន"</string>
<string name="category_personal" msgid="6236798763159385225">"ផ្ទាល់ខ្លួន"</string>
<string name="category_work" msgid="4014193632325996115">"ការងារ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ឯកជន"</string>
<string name="category_clone" msgid="1554511758987195974">"ក្លូន"</string>
<string name="development_settings_title" msgid="140296922921597393">"ជម្រើសសម្រាប់អ្នកអភិវឌ្ឍន៍"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"បើកដំណើរការជម្រើសអ្នកអភិវឌ្ឍន៍"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index ede347db975f..f9ec91f2a812 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿ"</string>
<string name="category_personal" msgid="6236798763159385225">"ವೈಯಕ್ತಿಕ"</string>
<string name="category_work" msgid="4014193632325996115">"ಕೆಲಸ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ಖಾಸಗಿ"</string>
<string name="category_clone" msgid="1554511758987195974">"ಕ್ಲೋನ್"</string>
<string name="development_settings_title" msgid="140296922921597393">"ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳು"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ಡೆವಲಪರ್ ಆಯ್ಕೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 78bd616dffe2..a1808805d0d8 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"프로필 선택"</string>
<string name="category_personal" msgid="6236798763159385225">"개인"</string>
<string name="category_work" msgid="4014193632325996115">"직장"</string>
+ <string name="category_private" msgid="4244892185452788977">"비공개"</string>
<string name="category_clone" msgid="1554511758987195974">"복사"</string>
<string name="development_settings_title" msgid="140296922921597393">"개발자 옵션"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"개발자 옵션 사용"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index a0b91230713d..b47356bd16c0 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профиль тандоо"</string>
<string name="category_personal" msgid="6236798763159385225">"Жеке"</string>
<string name="category_work" msgid="4014193632325996115">"Жумуш"</string>
+ <string name="category_private" msgid="4244892185452788977">"Купуя"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Иштеп чыгуучунун параметрлери"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Иштеп чыгуучунун параметрлерин иштетүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 96b2dc1a27cb..228eebe1c211 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ເລືອກໂປຣໄຟລ໌"</string>
<string name="category_personal" msgid="6236798763159385225">"​ສ່ວນ​ໂຕ"</string>
<string name="category_work" msgid="4014193632325996115">"​ບ່ອນ​ເຮັດ​ວຽກ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ສ່ວນຕົວ"</string>
<string name="category_clone" msgid="1554511758987195974">"ໂຄລນ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ຕົວເລືອກນັກພັດທະນາ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ເປີດໃຊ້ຕົວເລືອກນັກພັດທະນາ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 69630f436704..5dfd2f4afdc2 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profilio pasirinkimas"</string>
<string name="category_personal" msgid="6236798763159385225">"Asmeninės"</string>
<string name="category_work" msgid="4014193632325996115">"Darbo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privatus"</string>
<string name="category_clone" msgid="1554511758987195974">"Identiška kopija"</string>
<string name="development_settings_title" msgid="140296922921597393">"Kūrėjo parinktys"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Įgalinti kūrėjo parinktis"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 4f76ceeb2cca..8b55355c3224 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profila izvēlēšanās"</string>
<string name="category_personal" msgid="6236798763159385225">"Privāts"</string>
<string name="category_work" msgid="4014193632325996115">"Darba"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privāti"</string>
<string name="category_clone" msgid="1554511758987195974">"Klons"</string>
<string name="development_settings_title" msgid="140296922921597393">"Izstrādātāju opcijas"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Izstrādātāju opciju iespējošana"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 1c80c6502fc1..72fea47dd54e 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Изберете профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лични"</string>
<string name="category_work" msgid="4014193632325996115">"Работа"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватен"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Програмерски опции"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Овозможете ги програмерските опции"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index 31e3c83fb32f..6308083fc2e3 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക"</string>
<string name="category_personal" msgid="6236798763159385225">"വ്യക്തിപരം"</string>
<string name="category_work" msgid="4014193632325996115">"ഔദ്യോഗികം"</string>
+ <string name="category_private" msgid="4244892185452788977">"സ്വകാര്യം"</string>
<string name="category_clone" msgid="1554511758987195974">"ക്ലോൺ ചെയ്യുക"</string>
<string name="development_settings_title" msgid="140296922921597393">"ഡെവലപ്പർ ഓ‌പ്ഷനുകൾ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ഡെവലപ്പർ ഓ‌പ്ഷനുകൾ പ്രവർത്തനക്ഷമമാക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 5a636d9c8260..8707f40484a2 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Профайл сонгох"</string>
<string name="category_personal" msgid="6236798763159385225">"Хувийн"</string>
<string name="category_work" msgid="4014193632325996115">"Ажил"</string>
+ <string name="category_private" msgid="4244892185452788977">"Хувийн"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Хөгжүүлэгчийн тохиргоо"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Хөгжүүлэгчийн сонголтыг идэвхжүүлэх"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 4d78e571b47d..ab8b88cb10b1 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफाइल निवडा"</string>
<string name="category_personal" msgid="6236798763159385225">"वैयक्तिक"</string>
<string name="category_work" msgid="4014193632325996115">"कार्य"</string>
+ <string name="category_private" msgid="4244892185452788977">"खाजगी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन करा"</string>
<string name="development_settings_title" msgid="140296922921597393">"डेव्हलपर पर्याय"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"डेव्हलपर पर्याय सुरू करा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 2ae69bdff241..2841c7407f03 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Peribadi"</string>
<string name="category_work" msgid="4014193632325996115">"Tempat Kerja"</string>
+ <string name="category_private" msgid="4244892185452788977">"Peribadi"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pilihan pembangun"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Dayakan pilihan pembangun"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 7d6f2a75ba5a..d915370e8912 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ပရိုဖိုင်ကို ရွေးရန်"</string>
<string name="category_personal" msgid="6236798763159385225">"ကိုယ်ပိုင်"</string>
<string name="category_work" msgid="4014193632325996115">"အလုပ်"</string>
+ <string name="category_private" msgid="4244892185452788977">"သီးသန့်"</string>
<string name="category_clone" msgid="1554511758987195974">"ပုံတူပွားရန်"</string>
<string name="development_settings_title" msgid="140296922921597393">"ဆော့ဝဲလ်ရေးသူ ရွေးစရာများ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ဆော့ဖ်ဝဲရေးသူအတွက် ရွေးစရာများကို ဖွင့်ပါ"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9e550fb288e9..0a63c4bef4ef 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Velg profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personlig"</string>
<string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Utvikleralternativer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Slå på utvikleralternativer"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 1f6ad5bb863c..206444d7043a 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"प्रोफाइल रोज्नुहोस्"</string>
<string name="category_personal" msgid="6236798763159385225">"व्यक्तिगत"</string>
<string name="category_work" msgid="4014193632325996115">"काम"</string>
+ <string name="category_private" msgid="4244892185452788977">"निजी"</string>
<string name="category_clone" msgid="1554511758987195974">"क्लोन"</string>
<string name="development_settings_title" msgid="140296922921597393">"विकासकर्ताका विकल्पहरू"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"विकासकर्ता विकल्प सक्रिया गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index 6a6f184f77d1..af4797c9a2d0 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profiel kiezen"</string>
<string name="category_personal" msgid="6236798763159385225">"Persoonlijk"</string>
<string name="category_work" msgid="4014193632325996115">"Werk"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privé"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonen"</string>
<string name="development_settings_title" msgid="140296922921597393">"Ontwikkelaarsopties"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Opties voor ontwikkelaars aanzetten"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 980a37413f8b..bbdafc8b7656 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ପ୍ରୋଫାଇଲ୍‌ ବାଛନ୍ତୁ"</string>
<string name="category_personal" msgid="6236798763159385225">"ବ୍ୟକ୍ତିଗତ"</string>
<string name="category_work" msgid="4014193632325996115">"ୱାର୍କ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ପ୍ରାଇଭେଟ"</string>
<string name="category_clone" msgid="1554511758987195974">"କ୍ଲୋନ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ଡେଭଲପରଙ୍କ ପାଇଁ ବିକଳ୍ପଗୁଡ଼ିକ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ଡେଭଲପର୍‌ ବିକଳ୍ପଗୁଡ଼ିକ ସକ୍ଷମ କରନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index dd3fa15440e1..59eec15ce844 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ"</string>
<string name="category_personal" msgid="6236798763159385225">"ਨਿੱਜੀ"</string>
<string name="category_work" msgid="4014193632325996115">"ਕੰਮ ਸੰਬੰਧੀ"</string>
+ <string name="category_private" msgid="4244892185452788977">"ਨਿੱਜੀ"</string>
<string name="category_clone" msgid="1554511758987195974">"ਕਲੋਨ ਕਰੋ"</string>
<string name="development_settings_title" msgid="140296922921597393">"ਵਿਕਾਸਕਾਰ ਚੋਣਾਂ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ਵਿਕਾਸਕਾਰ ਵਿਕਲਪਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index f4ebd29b1276..d5d37f0d16e4 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Wybierz profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobiste"</string>
<string name="category_work" msgid="4014193632325996115">"Służbowe"</string>
+ <string name="category_private" msgid="4244892185452788977">"Prywatne"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opcje programisty"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Włącz opcje programisty"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index bb6c7d85fe62..3b0010f28efe 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Particular"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -231,7 +232,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 6bd5c2abccda..c817f8812660 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privado"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções de programador"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar as opções de programador"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index bb6c7d85fe62..3b0010f28efe 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string>
<string name="category_personal" msgid="6236798763159385225">"Pessoal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabalho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Particular"</string>
<string name="category_clone" msgid="1554511758987195974">"Clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opções do desenvolvedor"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Ativar opções do desenvolvedor"</string>
@@ -231,7 +232,7 @@
<string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Modo de depuração quando a rede Wi‑Fi estiver conectada"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Erro"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Depuração por Wi-Fi"</string>
- <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para ver e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
+ <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Para acessar e usar dispositivos disponíveis, ative a depuração por Wi-Fi."</string>
<string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Parear o dispositivo com um código QR"</string>
<string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Parear novos dispositivos usando um leitor de código QR"</string>
<string name="adb_pair_method_code_title" msgid="1122590300445142904">"Parear o dispositivo com um código de pareamento"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index f4399dc4e829..5d855a597d63 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Alege un profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Serviciu"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Clonează"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opțiuni pentru dezvoltatori"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Activează opțiunile pentru dezvoltatori"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 5d45e29ef9b6..9dac1d35826e 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Выбор профиля"</string>
<string name="category_personal" msgid="6236798763159385225">"Личный профиль"</string>
<string name="category_work" msgid="4014193632325996115">"Рабочий профиль"</string>
+ <string name="category_private" msgid="4244892185452788977">"Личный профиль"</string>
<string name="category_clone" msgid="1554511758987195974">"Клон"</string>
<string name="development_settings_title" msgid="140296922921597393">"Для разработчиков"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Включить параметры для разработчиков"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 996507d8c2f6..4b36694f603a 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"පැතිකඩ තෝරන්න"</string>
<string name="category_personal" msgid="6236798763159385225">"පෞද්ගලික"</string>
<string name="category_work" msgid="4014193632325996115">"කාර්යාලය"</string>
+ <string name="category_private" msgid="4244892185452788977">"පෞද්ගලික"</string>
<string name="category_clone" msgid="1554511758987195974">"ක්ලෝන කරන්න"</string>
<string name="development_settings_title" msgid="140296922921597393">"වර්ධක විකල්ප"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"සංවර්ධක විකල්ප සබල කිරීම"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index a51acd4fb227..30cc9fd37d5c 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Výber profilu"</string>
<string name="category_personal" msgid="6236798763159385225">"Osobné"</string>
<string name="category_work" msgid="4014193632325996115">"Pracovné"</string>
+ <string name="category_private" msgid="4244892185452788977">"Súkromné"</string>
<string name="category_clone" msgid="1554511758987195974">"Klonovanie"</string>
<string name="development_settings_title" msgid="140296922921597393">"Pre vývojárov"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Povolenie možností vývojára"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 0d7188dcbc50..127c00d296ac 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Izbira profila"</string>
<string name="category_personal" msgid="6236798763159385225">"Osebno"</string>
<string name="category_work" msgid="4014193632325996115">"Delo"</string>
+ <string name="category_private" msgid="4244892185452788977">"Zasebno"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Možnosti za razvijalce"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Omogočanje možnosti za razvijalce"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index c2bcce79b061..302e915bca98 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Zgjidh profilin"</string>
<string name="category_personal" msgid="6236798763159385225">"Personale"</string>
<string name="category_work" msgid="4014193632325996115">"Punë"</string>
+ <string name="category_private" msgid="4244892185452788977">"Private"</string>
<string name="category_clone" msgid="1554511758987195974">"Klono"</string>
<string name="development_settings_title" msgid="140296922921597393">"Opsionet e zhvilluesit"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivizo opsionet e zhvilluesit"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 09ff994477e1..4d4ae0b1b389 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Изаберите профил"</string>
<string name="category_personal" msgid="6236798763159385225">"Лично"</string>
<string name="category_work" msgid="4014193632325996115">"Посао"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватно"</string>
<string name="category_clone" msgid="1554511758987195974">"Клонирано"</string>
<string name="development_settings_title" msgid="140296922921597393">"Опције за програмере"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Омогући опције за програмере"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index fdd9d8c17d57..8fc6af6d80fb 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Välj profil"</string>
<string name="category_personal" msgid="6236798763159385225">"Privat"</string>
<string name="category_work" msgid="4014193632325996115">"Jobb"</string>
+ <string name="category_private" msgid="4244892185452788977">"Privat"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Utvecklaralternativ"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Aktivera utvecklaralternativ"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index bd4175289585..f6cb65450210 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Chagua wasifu"</string>
<string name="category_personal" msgid="6236798763159385225">"Binafsi"</string>
<string name="category_work" msgid="4014193632325996115">"Kazini"</string>
+ <string name="category_private" msgid="4244892185452788977">"Faragha"</string>
<string name="category_clone" msgid="1554511758987195974">"Kloni"</string>
<string name="development_settings_title" msgid="140296922921597393">"Chaguo za wasanidi"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Washa chaguo za wasanidi programu"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index fd379f0a80be..41788bfedced 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"சுயவிவரத்தைத் தேர்வு செய்க"</string>
<string name="category_personal" msgid="6236798763159385225">"தனிப்பட்டவை"</string>
<string name="category_work" msgid="4014193632325996115">"பணியிடம்"</string>
+ <string name="category_private" msgid="4244892185452788977">"தனிப்பட்டவை"</string>
<string name="category_clone" msgid="1554511758987195974">"குளோன்"</string>
<string name="development_settings_title" msgid="140296922921597393">"டெவெலப்பர் விருப்பங்கள்"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"டெவெலப்பர் விருப்பங்களை இயக்கு"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index 3e9d8be3cc1b..4c71251efe5e 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"ప్రొఫైల్‌ను ఎంచుకోండి"</string>
<string name="category_personal" msgid="6236798763159385225">"వ్యక్తిగతం"</string>
<string name="category_work" msgid="4014193632325996115">"వర్క్"</string>
+ <string name="category_private" msgid="4244892185452788977">"ప్రైవేట్"</string>
<string name="category_clone" msgid="1554511758987195974">"క్లోన్ చేయండి"</string>
<string name="development_settings_title" msgid="140296922921597393">"డెవలపర్ ఆప్షన్‌లు"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"డెవలపర్ ఎంపికలను ప్రారంభించండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index e67f37172bd7..cb6291a3b5e1 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"เลือกโปรไฟล์"</string>
<string name="category_personal" msgid="6236798763159385225">"ส่วนตัว"</string>
<string name="category_work" msgid="4014193632325996115">"งาน"</string>
+ <string name="category_private" msgid="4244892185452788977">"ส่วนตัว"</string>
<string name="category_clone" msgid="1554511758987195974">"โคลน"</string>
<string name="development_settings_title" msgid="140296922921597393">"ตัวเลือกสำหรับนักพัฒนาแอป"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"เปิดใช้ตัวเลือกสำหรับนักพัฒนาแอป"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 440bbe768c05..44d5de244076 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Pumili ng profile"</string>
<string name="category_personal" msgid="6236798763159385225">"Personal"</string>
<string name="category_work" msgid="4014193632325996115">"Trabaho"</string>
+ <string name="category_private" msgid="4244892185452788977">"Pribado"</string>
<string name="category_clone" msgid="1554511758987195974">"I-clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Mga opsyon ng developer"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"I-enable ang mga opsyon ng developer"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index b38012fbbd8c..e33afa1e0693 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -141,7 +141,7 @@
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"İptal"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Eşleme işlemi, bağlantı kurulduğunda kişilerinize ve çağrı geçmişine erişim izni verir."</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya şifre anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya geçiş anahtarı yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile iletişim kurulamıyor."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Eşleme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> tarafından reddedildi."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Bilgisayar"</string>
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string>
<string name="category_personal" msgid="6236798763159385225">"Kişisel"</string>
<string name="category_work" msgid="4014193632325996115">"İş"</string>
+ <string name="category_private" msgid="4244892185452788977">"Gizli"</string>
<string name="category_clone" msgid="1554511758987195974">"Klon"</string>
<string name="development_settings_title" msgid="140296922921597393">"Geliştirici seçenekleri"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Geliştirici seçeneklerini etkinleştir"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 4f0854790e92..319caca7b795 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Вибрати профіль"</string>
<string name="category_personal" msgid="6236798763159385225">"Особисте"</string>
<string name="category_work" msgid="4014193632325996115">"Робоче"</string>
+ <string name="category_private" msgid="4244892185452788977">"Приватні"</string>
<string name="category_clone" msgid="1554511758987195974">"Копія профілю"</string>
<string name="development_settings_title" msgid="140296922921597393">"Параметри розробника"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Увімкнути параметри розробника"</string>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index ce67d1516273..10dacded0d56 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"پروفائل منتخب کریں"</string>
<string name="category_personal" msgid="6236798763159385225">"ذاتی"</string>
<string name="category_work" msgid="4014193632325996115">"دفتر"</string>
+ <string name="category_private" msgid="4244892185452788977">"نجی"</string>
<string name="category_clone" msgid="1554511758987195974">"کلون کریں"</string>
<string name="development_settings_title" msgid="140296922921597393">"ڈویلپر کے اختیارات"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"ڈویلپر کے اختیارات فعال کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 77da981b29a9..0a85c280e2d3 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Profilni tanlang"</string>
<string name="category_personal" msgid="6236798763159385225">"Shaxsiy"</string>
<string name="category_work" msgid="4014193632325996115">"Ish"</string>
+ <string name="category_private" msgid="4244892185452788977">"Yopiq"</string>
<string name="category_clone" msgid="1554511758987195974">"Nusxalash"</string>
<string name="development_settings_title" msgid="140296922921597393">"Dasturchi sozlamalari"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Dasturchi sozlamalarini yoqish"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index bf510f6522f3..3c34f4dd7234 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Chọn hồ sơ"</string>
<string name="category_personal" msgid="6236798763159385225">"Cá nhân"</string>
<string name="category_work" msgid="4014193632325996115">"Công việc"</string>
+ <string name="category_private" msgid="4244892185452788977">"Riêng tư"</string>
<string name="category_clone" msgid="1554511758987195974">"Nhân bản"</string>
<string name="development_settings_title" msgid="140296922921597393">"Tùy chọn cho nhà phát triển"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Bật tùy chọn nhà phát triển"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 8e3145ace05a..d385e67b4e7f 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"选择个人资料"</string>
<string name="category_personal" msgid="6236798763159385225">"个人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私享"</string>
<string name="category_clone" msgid="1554511758987195974">"克隆"</string>
<string name="development_settings_title" msgid="140296922921597393">"开发者选项"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"启用开发者选项"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index aa9f21f28b91..adcb4d8420eb 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</string>
<string name="category_personal" msgid="6236798763159385225">"個人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私人"</string>
<string name="category_clone" msgid="1554511758987195974">"複製"</string>
<string name="development_settings_title" msgid="140296922921597393">"開發人員選項"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 3c65a4d7d5ee..fddef2b1d15d 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"選擇設定檔"</string>
<string name="category_personal" msgid="6236798763159385225">"個人"</string>
<string name="category_work" msgid="4014193632325996115">"工作"</string>
+ <string name="category_private" msgid="4244892185452788977">"私人"</string>
<string name="category_clone" msgid="1554511758987195974">"複製"</string>
<string name="development_settings_title" msgid="140296922921597393">"開發人員選項"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"啟用開發人員選項"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 08b04cc4b1bf..82b7306ba3e0 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -216,6 +216,7 @@
<string name="choose_profile" msgid="343803890897657450">"Khetha iphrofayela"</string>
<string name="category_personal" msgid="6236798763159385225">"Okomuntu siqu"</string>
<string name="category_work" msgid="4014193632325996115">"Umsebenzi"</string>
+ <string name="category_private" msgid="4244892185452788977">"Okuyimfihlo"</string>
<string name="category_clone" msgid="1554511758987195974">"Yenza i-clone"</string>
<string name="development_settings_title" msgid="140296922921597393">"Izinketho Zonjiniyela"</string>
<string name="development_settings_enable" msgid="4285094651288242183">"Nika amandla izinketho zonjiniyela"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 57867be53bb9..f83e37b2fd5c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -19,6 +19,9 @@ package com.android.settingslib.bluetooth;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -28,10 +31,11 @@ import android.content.Context;
import android.os.Build;
import android.util.Log;
+import androidx.annotation.RequiresApi;
+
import java.util.ArrayList;
import java.util.List;
-
-import androidx.annotation.RequiresApi;
+import java.util.concurrent.Executor;
/**
* VolumeControlProfile handles Bluetooth Volume Control Controller role
@@ -102,6 +106,88 @@ public class VolumeControlProfile implements LocalBluetoothProfile {
BluetoothProfile.VOLUME_CONTROL);
}
+
+ /**
+ * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the
+ * operation of this profile.
+ *
+ * Repeated registration of the same <var>callback</var> object will have no effect after
+ * the first call to this method, even when the <var>executor</var> is different. API caller
+ * would have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with
+ * the same callback object before registering it again.
+ *
+ * @param executor an {@link Executor} to execute given callback
+ * @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
+ * @throws IllegalArgumentException if a null executor or callback is given
+ */
+ public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothVolumeControl.Callback callback) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot register callback.");
+ return;
+ }
+ mService.registerCallback(executor, callback);
+ }
+
+ /**
+ * Unregisters the specified {@link BluetoothVolumeControl.Callback}.
+ * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling
+ * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
+ *
+ * <p>Callbacks are automatically unregistered when application process goes away
+ *
+ * @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
+ * @throws IllegalArgumentException when callback is null or when no callback is registered
+ */
+ public void unregisterCallback(@NonNull BluetoothVolumeControl.Callback callback) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot unregister callback.");
+ return;
+ }
+ mService.unregisterCallback(callback);
+ }
+
+ /**
+ * Tells the remote device to set a volume offset to the absolute volume.
+ *
+ * @param device {@link BluetoothDevice} representing the remote device
+ * @param volumeOffset volume offset to be set on the remote device
+ */
+ public void setVolumeOffset(BluetoothDevice device,
+ @IntRange(from = -255, to = 255) int volumeOffset) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
+ return;
+ }
+ if (device == null) {
+ Log.w(TAG, "Device is null. Cannot set volume offset.");
+ return;
+ }
+ mService.setVolumeOffset(device, volumeOffset);
+ }
+
+ /**
+ * Provides information about the possibility to set volume offset on the remote device.
+ * If the remote device supports Volume Offset Control Service, it is automatically
+ * connected.
+ *
+ * @param device {@link BluetoothDevice} representing the remote device
+ * @return {@code true} if volume offset function is supported and available to use on the
+ * remote device. When Bluetooth is off, the return value should always be
+ * {@code false}.
+ */
+ public boolean isVolumeOffsetAvailable(BluetoothDevice device) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot get is volume offset available.");
+ return false;
+ }
+ if (device == null) {
+ Log.w(TAG, "Device is null. Cannot get is volume offset available.");
+ return false;
+ }
+ return mService.isVolumeOffsetAvailable(device);
+ }
+
@Override
public boolean accessProfileEnabled() {
return false;
@@ -113,12 +199,12 @@ public class VolumeControlProfile implements LocalBluetoothProfile {
}
/**
- * Get VolumeControlProfile devices matching connection states{
+ * Gets VolumeControlProfile devices matching connection states{
+ * {@code BluetoothProfile.STATE_CONNECTED},
+ * {@code BluetoothProfile.STATE_CONNECTING},
+ * {@code BluetoothProfile.STATE_DISCONNECTING}}
*
* @return Matching device list
- * @code BluetoothProfile.STATE_CONNECTED,
- * @code BluetoothProfile.STATE_CONNECTING,
- * @code BluetoothProfile.STATE_DISCONNECTING}
*/
public List<BluetoothDevice> getConnectedDevices() {
if (mService == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
index a5f69ffec4b4..02d76304f077 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt
@@ -18,7 +18,7 @@ package com.android.settingslib.core.instrumentation
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.preference.PreferenceGroupAdapter
-import androidx.preference.SwitchPreference
+import androidx.preference.TwoStatePreference
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.jank.InteractionJankMonitor
import java.util.concurrent.Executors
@@ -43,7 +43,10 @@ object SettingsJankMonitor {
* @param preference the clicked preference
*/
@JvmStatic
- fun detectSwitchPreferenceClickJank(recyclerView: RecyclerView, preference: SwitchPreference) {
+ fun detectSwitchPreferenceClickJank(
+ recyclerView: RecyclerView,
+ preference: TwoStatePreference,
+ ) {
val adapter = recyclerView.adapter as? PreferenceGroupAdapter ?: return
val adapterPosition = adapter.getPreferenceAdapterPosition(preference)
val viewHolder = recyclerView.findViewHolderForAdapterPosition(adapterPosition) ?: return
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index e38e041a87dc..2a2841745fa0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -39,39 +39,50 @@ public class DeviceIconUtil {
@DrawableRes private static final int DEFAULT_ICON = R.drawable.ic_smartphone;
public DeviceIconUtil() {
- List<Device> deviceList = Arrays.asList(
- new Device(
- AudioDeviceInfo.TYPE_USB_DEVICE,
- MediaRoute2Info.TYPE_USB_DEVICE,
- R.drawable.ic_headphone),
- new Device(
- AudioDeviceInfo.TYPE_USB_HEADSET,
- MediaRoute2Info.TYPE_USB_HEADSET,
- R.drawable.ic_headphone),
- new Device(
- AudioDeviceInfo.TYPE_USB_ACCESSORY,
- MediaRoute2Info.TYPE_USB_ACCESSORY,
- R.drawable.ic_headphone),
- new Device(
- AudioDeviceInfo.TYPE_DOCK,
- MediaRoute2Info.TYPE_DOCK,
- R.drawable.ic_dock_device),
- new Device(
- AudioDeviceInfo.TYPE_HDMI,
- MediaRoute2Info.TYPE_HDMI,
- R.drawable.ic_headphone),
- new Device(
- AudioDeviceInfo.TYPE_WIRED_HEADSET,
- MediaRoute2Info.TYPE_WIRED_HEADSET,
- R.drawable.ic_headphone),
- new Device(
- AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
- MediaRoute2Info.TYPE_WIRED_HEADPHONES,
- R.drawable.ic_headphone),
- new Device(
- AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
- MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
- R.drawable.ic_smartphone));
+ List<Device> deviceList =
+ Arrays.asList(
+ new Device(
+ AudioDeviceInfo.TYPE_USB_DEVICE,
+ MediaRoute2Info.TYPE_USB_DEVICE,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_USB_HEADSET,
+ MediaRoute2Info.TYPE_USB_HEADSET,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_USB_ACCESSORY,
+ MediaRoute2Info.TYPE_USB_ACCESSORY,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_DOCK,
+ MediaRoute2Info.TYPE_DOCK,
+ R.drawable.ic_dock_device),
+ new Device(
+ AudioDeviceInfo.TYPE_HDMI,
+ MediaRoute2Info.TYPE_HDMI,
+ R.drawable.ic_headphone),
+ // TODO: b/306359110 - Put proper iconography for HDMI_ARC type.
+ new Device(
+ AudioDeviceInfo.TYPE_HDMI_ARC,
+ MediaRoute2Info.TYPE_HDMI_ARC,
+ R.drawable.ic_headphone),
+ // TODO: b/306359110 - Put proper iconography for HDMI_EARC type.
+ new Device(
+ AudioDeviceInfo.TYPE_HDMI_EARC,
+ MediaRoute2Info.TYPE_HDMI_EARC,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_WIRED_HEADSET,
+ MediaRoute2Info.TYPE_WIRED_HEADSET,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+ MediaRoute2Info.TYPE_WIRED_HEADPHONES,
+ R.drawable.ic_headphone),
+ new Device(
+ AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+ MediaRoute2Info.TYPE_BUILTIN_SPEAKER,
+ R.drawable.ic_smartphone));
for (int i = 0; i < deviceList.size(); i++) {
Device device = deviceList.get(i);
mAudioDeviceTypeToIconMap.put(device.mAudioDeviceType, device);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bf63f5d80450..5dacba5357cd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -21,6 +21,8 @@ import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_DOCK;
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_CAR;
@@ -635,6 +637,8 @@ public abstract class InfoMediaManager extends MediaManager {
case TYPE_USB_ACCESSORY:
case TYPE_DOCK:
case TYPE_HDMI:
+ case TYPE_HDMI_ARC:
+ case TYPE_HDMI_EARC:
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
mediaDevice =
@@ -668,11 +672,12 @@ public abstract class InfoMediaManager extends MediaManager {
route,
mPackageName,
mPreferenceItemMap.get(route.getId()));
+ break;
default:
Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
break;
-
}
+
if (mediaDevice != null && !TextUtils.isEmpty(mPackageName)
&& getRoutingSessionInfo().getSelectedRoutes().contains(route.getId())) {
mediaDevice.setState(STATE_SELECTED);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 147412d08d2c..8085c998abea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -21,6 +21,8 @@ import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_DOCK;
import static android.media.MediaRoute2Info.TYPE_GROUP;
import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
import static android.media.MediaRoute2Info.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER;
import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
@@ -140,7 +142,6 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
return;
}
-
switch (info.getType()) {
case TYPE_GROUP:
mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
@@ -157,6 +158,8 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
case TYPE_USB_ACCESSORY:
case TYPE_DOCK:
case TYPE_HDMI:
+ case TYPE_HDMI_ARC:
+ case TYPE_HDMI_EARC:
mType = MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE;
break;
case TYPE_HEARING_AID:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index a63bbdf36fa8..c44f66e99d00 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -18,6 +18,8 @@ package com.android.settingslib.media;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_DOCK;
import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
import static android.media.MediaRoute2Info.TYPE_USB_ACCESSORY;
import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
@@ -71,6 +73,8 @@ public class PhoneMediaDevice extends MediaDevice {
name = context.getString(R.string.media_transfer_this_device_name);
break;
case TYPE_HDMI:
+ case TYPE_HDMI_ARC:
+ case TYPE_HDMI_EARC:
name = context.getString(R.string.media_transfer_external_device_name);
break;
default:
@@ -144,6 +148,8 @@ public class PhoneMediaDevice extends MediaDevice {
case TYPE_USB_ACCESSORY:
case TYPE_DOCK:
case TYPE_HDMI:
+ case TYPE_HDMI_ARC:
+ case TYPE_HDMI_EARC:
id = USB_HEADSET_ID;
break;
case TYPE_BUILTIN_SPEAKER:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 70956e9221e2..9ab3b478a444 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -38,6 +38,7 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/** Implements {@link InfoMediaManager} using {@link MediaRouter2}. */
@@ -54,8 +55,11 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
private final RouteCallback mRouteCallback = new RouteCallback();
private final TransferCallback mTransferCallback = new TransferCallback();
private final ControllerCallback mControllerCallback = new ControllerCallback();
- private final RouteListingPreferenceCallback mRouteListingPreferenceCallback =
- new RouteListingPreferenceCallback();
+ private final Consumer<RouteListingPreference> mRouteListingPreferenceCallback =
+ (preference) -> {
+ notifyRouteListingPreferenceUpdated(preference);
+ refreshDevices();
+ };
// TODO: b/192657812 - Create factory method in InfoMediaManager to return
// RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
@@ -83,7 +87,8 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
@Override
protected void startScanOnRouter() {
mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY);
- mRouter.registerRouteListingPreferenceCallback(mExecutor, mRouteListingPreferenceCallback);
+ mRouter.registerRouteListingPreferenceUpdatedCallback(
+ mExecutor, mRouteListingPreferenceCallback);
mRouter.registerTransferCallback(mExecutor, mTransferCallback);
mRouter.registerControllerCallback(mExecutor, mControllerCallback);
mRouter.startScan();
@@ -94,7 +99,7 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
mRouter.stopScan();
mRouter.unregisterControllerCallback(mControllerCallback);
mRouter.unregisterTransferCallback(mTransferCallback);
- mRouter.unregisterRouteListingPreferenceCallback(mRouteListingPreferenceCallback);
+ mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback);
mRouter.unregisterRouteCallback(mRouteCallback);
}
@@ -308,13 +313,4 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
refreshDevices();
}
}
-
- private final class RouteListingPreferenceCallback
- extends MediaRouter2.RouteListingPreferenceCallback {
- @Override
- public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {
- notifyRouteListingPreferenceUpdated(preference);
- refreshDevices();
- }
- }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
new file mode 100644
index 000000000000..c56062739735
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class VolumeControlProfileTest {
+
+ private static final int TEST_VOLUME_OFFSET = 10;
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private CachedBluetoothDeviceManager mDeviceManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private BluetoothDevice mBluetoothDevice;
+ @Mock
+ private BluetoothVolumeControl mService;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private BluetoothProfile.ServiceListener mServiceListener;
+ private VolumeControlProfile mProfile;
+
+ @Before
+ public void setUp() {
+ mProfile = new VolumeControlProfile(mContext, mDeviceManager, mProfileManager);
+ final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+ final ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(bluetoothManager.getAdapter());
+ mServiceListener = shadowBluetoothAdapter.getServiceListener();
+ }
+
+ @Test
+ public void onServiceConnected_isProfileReady() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ assertThat(mProfile.isProfileReady()).isTrue();
+ verify(mProfileManager).callServiceConnectedListeners();
+ }
+
+ @Test
+ public void onServiceDisconnected_isProfileNotReady() {
+ mServiceListener.onServiceDisconnected(BluetoothProfile.VOLUME_CONTROL);
+
+ assertThat(mProfile.isProfileReady()).isFalse();
+ verify(mProfileManager).callServiceDisconnectedListeners();
+ }
+
+ @Test
+ public void getConnectionStatus_returnCorrectConnectionState() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionState(mBluetoothDevice))
+ .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+ assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+ }
+
+ @Test
+ public void isEnabled_connectionPolicyForbidden_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+ assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+ assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+ .isEqualTo(CONNECTION_POLICY_ALLOWED);
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.getConnectionPolicy(mBluetoothDevice))
+ .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+ when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+
+ assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+ }
+
+ @Test
+ public void getConnectedDevices_returnCorrectList() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ int[] connectedStates = new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING};
+ List<BluetoothDevice> connectedList = Arrays.asList(
+ mBluetoothDevice,
+ mBluetoothDevice,
+ mBluetoothDevice);
+ when(mService.getDevicesMatchingConnectionStates(connectedStates))
+ .thenReturn(connectedList);
+
+ assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+ }
+
+ @Test
+ public void registerCallback_verifyIsCalled() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ final Executor executor = (command -> new Thread(command).start());
+ final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ mProfile.registerCallback(executor, callback);
+
+ verify(mService).registerCallback(executor, callback);
+ }
+
+ @Test
+ public void unregisterCallback_verifyIsCalled() {
+ final BluetoothVolumeControl.Callback callback = (device, volumeOffset) -> {};
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ mProfile.unregisterCallback(callback);
+
+ verify(mService).unregisterCallback(callback);
+ }
+
+ @Test
+ public void setVolumeOffset_verifyIsCalled() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ mProfile.setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
+
+ verify(mService).setVolumeOffset(mBluetoothDevice, TEST_VOLUME_OFFSET);
+ }
+
+ @Test
+ public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true);
+
+ final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
+
+ verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
+ assertThat(available).isTrue();
+ }
+
+ @Test
+ public void isVolumeOffsetAvailable_verifyIsCalledAndReturnFalse() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+ when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(false);
+
+ final boolean available = mProfile.isVolumeOffsetAvailable(mBluetoothDevice);
+
+ verify(mService).isVolumeOffsetAvailable(mBluetoothDevice);
+ assertThat(available).isFalse();
+ }
+}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index f4ca260d4d89..a4a9290b16ab 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -17,9 +17,10 @@ license {
],
}
-android_app {
- name: "SettingsProvider",
+android_library {
+ name: "SettingsProviderLib",
defaults: ["platform_app_defaults"],
+ manifest: "AndroidManifestLib.xml",
resource_dirs: ["res"],
srcs: [
"src/**/*.java",
@@ -32,38 +33,37 @@ android_app {
],
static_libs: [
"device_config_service_flags_java",
- "junit",
"SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayUtils",
],
platform_apis: true,
+}
+
+android_app {
+ name: "SettingsProvider",
+ defaults: ["platform_app_defaults"],
+ resource_dirs: [],
+ static_libs: ["SettingsProviderLib"],
+ platform_apis: true,
certificate: "platform",
privileged: true,
}
android_test {
name: "SettingsProviderTest",
- // Note we statically link several classes to do some unit tests. It's not accessible otherwise
- // because this test is not an instrumentation test. (because the target runs in the system process.)
srcs: [
"test/**/*.java",
- "src/android/provider/settings/backup/*",
- "src/android/provider/settings/validators/*",
- "src/com/android/providers/settings/GenerationRegistry.java",
- "src/com/android/providers/settings/SettingsBackupAgent.java",
- "src/com/android/providers/settings/SettingsState.java",
- "src/com/android/providers/settings/SettingsHelper.java",
- "src/com/android/providers/settings/WifiSoftApConfigChangedNotifier.java",
],
static_libs: [
+ // Note we statically link SettingsProviderLib to do some unit tests. It's not accessible otherwise
+ // because this test is not an instrumentation test. (because the target runs in the system process.)
+ "SettingsProviderLib",
+
"androidx.test.rules",
- "device_config_service_flags_java",
"flag-junit",
+ "junit",
"mockito-target-minus-junit4",
"platform-test-annotations",
- "SettingsLibDeviceStateRotationLock",
- "SettingsLibDisplayUtils",
- "platform-test-annotations",
"truth",
],
libs: [
@@ -71,12 +71,7 @@ android_test {
"android.test.mock",
"unsupportedappusage",
],
- resource_dirs: ["res"],
- aaptflags: [
- "--auto-add-overlay",
- "--extra-packages",
- "com.android.providers.settings",
- ],
+ resource_dirs: [],
platform_apis: true,
certificate: "platform",
test_suites: ["device-tests"],
diff --git a/packages/SettingsProvider/AndroidManifestLib.xml b/packages/SettingsProvider/AndroidManifestLib.xml
new file mode 100644
index 000000000000..a20b539e2c10
--- /dev/null
+++ b/packages/SettingsProvider/AndroidManifestLib.xml
@@ -0,0 +1,2 @@
+<manifest package="com.android.providers.settings">
+</manifest>
diff --git a/packages/SoundPicker/res/values-af/strings.xml b/packages/SoundPicker/res/values-af/strings.xml
index 7396b76878a8..fd857b1c3cc4 100644
--- a/packages/SoundPicker/res/values-af/strings.xml
+++ b/packages/SoundPicker/res/values-af/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan nie gepasmaakte luitoon byvoeg nie"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan nie gepasmaakte luitoon uitvee nie"</string>
<string name="app_label" msgid="3091611356093417332">"Klanke"</string>
- <string name="empty_list" msgid="2871978423955821191">"Die lys is leeg"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Klank"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasie"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-am/strings.xml b/packages/SoundPicker/res/values-am/strings.xml
index bd1c24bfc43d..85206c0b46a0 100644
--- a/packages/SoundPicker/res/values-am/strings.xml
+++ b/packages/SoundPicker/res/values-am/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ብጁ የጥሪ ቅላጼን ማከል አልተቻለም"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ብጁ የጥሪ ቅላጼን መሰረዝ አልተቻለም"</string>
<string name="app_label" msgid="3091611356093417332">"ድምፆች"</string>
- <string name="empty_list" msgid="2871978423955821191">"ዝርዝሩ ባዶ ነው"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ድምፅ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ንዝረት"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ar/strings.xml b/packages/SoundPicker/res/values-ar/strings.xml
index 805c7cf5955a..f8844e94815f 100644
--- a/packages/SoundPicker/res/values-ar/strings.xml
+++ b/packages/SoundPicker/res/values-ar/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"يتعذر إضافة نغمة رنين مخصصة"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"يتعذر حذف نغمة الرنين المخصصة"</string>
<string name="app_label" msgid="3091611356093417332">"الأصوات"</string>
- <string name="empty_list" msgid="2871978423955821191">"القائمة فارغة."</string>
- <string name="sound_page_title" msgid="2143312098775103522">"الصوت"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"الاهتزاز"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-as/strings.xml b/packages/SoundPicker/res/values-as/strings.xml
index 0a1cd1bb1dee..5d6bc5d603b4 100644
--- a/packages/SoundPicker/res/values-as/strings.xml
+++ b/packages/SoundPicker/res/values-as/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন যোগ কৰিব পৰা নগ\'ল"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"নিজৰ উপযোগিতা অনুযায়ী তৈয়াৰ কৰা ৰিংট\'ন মচিব পৰা নগ\'ল"</string>
<string name="app_label" msgid="3091611356093417332">"ধ্বনিসমূহ"</string>
- <string name="empty_list" msgid="2871978423955821191">"সূচীখন খালী আছে"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ধ্বনি"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"কম্পন"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-az/strings.xml b/packages/SoundPicker/res/values-az/strings.xml
index a308329ef373..e32c3eb5d9e7 100644
--- a/packages/SoundPicker/res/values-az/strings.xml
+++ b/packages/SoundPicker/res/values-az/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Fərdi zəng səsi əlavə etmək mümkün deyil"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Fərdi zəng səsini silmək mümkün deyil"</string>
<string name="app_label" msgid="3091611356093417332">"Səslər"</string>
- <string name="empty_list" msgid="2871978423955821191">"Siyahı boşdur"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Səs"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrasiya"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
index 2a7a19645224..947c85c8ad91 100644
--- a/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
+++ b/packages/SoundPicker/res/values-b+sr+Latn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije uspelo"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije uspelo"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-be/strings.xml b/packages/SoundPicker/res/values-be/strings.xml
index 431a301faa2e..6f7fc6882d76 100644
--- a/packages/SoundPicker/res/values-be/strings.xml
+++ b/packages/SoundPicker/res/values-be/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Немагчыма дадаць карыстальніцкі рынгтон"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Немагчыма выдаліць карыстальніцкі рынгтон"</string>
<string name="app_label" msgid="3091611356093417332">"Гукі"</string>
- <string name="empty_list" msgid="2871978423955821191">"Спіс пусты"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Гук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вібрацыя"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bg/strings.xml b/packages/SoundPicker/res/values-bg/strings.xml
index 7447af6a29ff..4277d2851e8c 100644
--- a/packages/SoundPicker/res/values-bg/strings.xml
+++ b/packages/SoundPicker/res/values-bg/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Персонализираната мелодия не може да се добави"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Персонализираната мелодия не може да се изтрие"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"Списъкът е празен"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибриране"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bn/strings.xml b/packages/SoundPicker/res/values-bn/strings.xml
index c31b36a4c799..276594ad4c77 100644
--- a/packages/SoundPicker/res/values-bn/strings.xml
+++ b/packages/SoundPicker/res/values-bn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"কাস্টম রিংটোন যোগ করা গেল না"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"কাস্টম রিংটোন মোছা গেল না"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"তালিকায় কিছু নেই"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"সাউন্ড"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ভাইব্রেশন"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-bs/strings.xml b/packages/SoundPicker/res/values-bs/strings.xml
index e65b90d8ae9a..0c8d33f4187e 100644
--- a/packages/SoundPicker/res/values-bs/strings.xml
+++ b/packages/SoundPicker/res/values-bs/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nije moguće dodati prilagođenu melodiju zvona"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nije moguće izbrisati prilagođenu melodiju zvona"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista je prazna"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ca/strings.xml b/packages/SoundPicker/res/values-ca/strings.xml
index 33839bc026a4..ed96f70c7c01 100644
--- a/packages/SoundPicker/res/values-ca/strings.xml
+++ b/packages/SoundPicker/res/values-ca/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No es pot afegir el so de trucada personalitzat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No es pot suprimir el so de trucada personalitzat"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"La llista és buida"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"So"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibració"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-cs/strings.xml b/packages/SoundPicker/res/values-cs/strings.xml
index 612a6b214baa..dc67c960cca9 100644
--- a/packages/SoundPicker/res/values-cs/strings.xml
+++ b/packages/SoundPicker/res/values-cs/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Vlastní vyzvánění se nepodařilo přidat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Vlastní vyzvánění se nepodařilo smazat"</string>
<string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
- <string name="empty_list" msgid="2871978423955821191">"Seznam je prázdný"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrace"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-da/strings.xml b/packages/SoundPicker/res/values-da/strings.xml
index 56c4d23577a1..b4437dc805ac 100644
--- a/packages/SoundPicker/res/values-da/strings.xml
+++ b/packages/SoundPicker/res/values-da/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Den tilpassede ringetone kunne ikke tilføjes"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Den tilpassede ringetone kunne ikke slettes"</string>
<string name="app_label" msgid="3091611356093417332">"Lyde"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-de/strings.xml b/packages/SoundPicker/res/values-de/strings.xml
index b004e2a17a9f..8be3aaab7eb7 100644
--- a/packages/SoundPicker/res/values-de/strings.xml
+++ b/packages/SoundPicker/res/values-de/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Benutzerdefinierter Klingelton konnte nicht hinzugefügt werden"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Benutzerdefinierter Klingelton konnte nicht gelöscht werden"</string>
<string name="app_label" msgid="3091611356093417332">"Töne"</string>
- <string name="empty_list" msgid="2871978423955821191">"Die Liste ist leer"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ton"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-el/strings.xml b/packages/SoundPicker/res/values-el/strings.xml
index bbcb1f52fd43..41e9b0ccf334 100644
--- a/packages/SoundPicker/res/values-el/strings.xml
+++ b/packages/SoundPicker/res/values-el/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Δεν είναι δυνατή η προσθήκη προσαρμοσμένου ήχου κλήσης"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Δεν είναι δυνατή η διαγραφή προσαρμοσμένου ήχου κλήσης"</string>
<string name="app_label" msgid="3091611356093417332">"Ήχοι"</string>
- <string name="empty_list" msgid="2871978423955821191">"Η λίστα είναι κενή"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ήχος"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Δόνηση"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rAU/strings.xml b/packages/SoundPicker/res/values-en-rAU/strings.xml
index 5030314f6d0f..4c237b993e07 100644
--- a/packages/SoundPicker/res/values-en-rAU/strings.xml
+++ b/packages/SoundPicker/res/values-en-rAU/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rCA/strings.xml b/packages/SoundPicker/res/values-en-rCA/strings.xml
index d88708214c6f..b070835635fe 100644
--- a/packages/SoundPicker/res/values-en-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-en-rCA/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rGB/strings.xml b/packages/SoundPicker/res/values-en-rGB/strings.xml
index 5030314f6d0f..4c237b993e07 100644
--- a/packages/SoundPicker/res/values-en-rGB/strings.xml
+++ b/packages/SoundPicker/res/values-en-rGB/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rIN/strings.xml b/packages/SoundPicker/res/values-en-rIN/strings.xml
index 5030314f6d0f..4c237b993e07 100644
--- a/packages/SoundPicker/res/values-en-rIN/strings.xml
+++ b/packages/SoundPicker/res/values-en-rIN/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add customised ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete customised ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"The list is empty"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sound"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-en-rXC/strings.xml b/packages/SoundPicker/res/values-en-rXC/strings.xml
index d26c89b4b902..8397e0bb639a 100644
--- a/packages/SoundPicker/res/values-en-rXC/strings.xml
+++ b/packages/SoundPicker/res/values-en-rXC/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎Unable to add custom ringtone‎‏‎‎‏‎"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎Unable to delete custom ringtone‎‏‎‎‏‎"</string>
<string name="app_label" msgid="3091611356093417332">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‏‏‎‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎Sounds‎‏‎‎‏‎"</string>
- <string name="empty_list" msgid="2871978423955821191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‏‏‎The list is empty‎‏‎‎‏‎"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‏‎‎‎‏‎‎Sound‎‏‎‎‏‎"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎Vibration‎‏‎‎‏‎"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-es-rUS/strings.xml b/packages/SoundPicker/res/values-es-rUS/strings.xml
index 211bfed0d369..5bf73b2fe2b8 100644
--- a/packages/SoundPicker/res/values-es-rUS/strings.xml
+++ b/packages/SoundPicker/res/values-es-rUS/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se puede agregar el tono personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se puede borrar el tono personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
- <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-es/strings.xml b/packages/SoundPicker/res/values-es/strings.xml
index 3fdad745ff0c..a77f656f14e5 100644
--- a/packages/SoundPicker/res/values-es/strings.xml
+++ b/packages/SoundPicker/res/values-es/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"No se ha podido añadir un tono de llamada personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"No se ha podido eliminar un tono de llamada personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sonidos"</string>
- <string name="empty_list" msgid="2871978423955821191">"La lista está vacía"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sonido"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-et/strings.xml b/packages/SoundPicker/res/values-et/strings.xml
index 3d9ee945005d..fa680accddf5 100644
--- a/packages/SoundPicker/res/values-et/strings.xml
+++ b/packages/SoundPicker/res/values-et/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kohandatud helinat ei õnnestu lisada"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kohandatud helinat ei õnnestu kustutada"</string>
<string name="app_label" msgid="3091611356093417332">"Helid"</string>
- <string name="empty_list" msgid="2871978423955821191">"See loend on tühi"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Heli"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibreerimine"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-eu/strings.xml b/packages/SoundPicker/res/values-eu/strings.xml
index 1f929bf57bdd..e8e07fe00d47 100644
--- a/packages/SoundPicker/res/values-eu/strings.xml
+++ b/packages/SoundPicker/res/values-eu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ezin da gehitu tonu pertsonalizatua"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ezin da ezabatu tonu pertsonalizatua"</string>
<string name="app_label" msgid="3091611356093417332">"Soinuak"</string>
- <string name="empty_list" msgid="2871978423955821191">"Zerrenda hutsik dago"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Soinua"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Dardara"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fa/strings.xml b/packages/SoundPicker/res/values-fa/strings.xml
index 3b75ac90def2..769d5d53b657 100644
--- a/packages/SoundPicker/res/values-fa/strings.xml
+++ b/packages/SoundPicker/res/values-fa/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"افزودن آهنگ زنگ سفارشی ممکن نیست"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حذف آهنگ زنگ سفارشی ممکن نیست"</string>
<string name="app_label" msgid="3091611356093417332">"صداها"</string>
- <string name="empty_list" msgid="2871978423955821191">"فهرست خالی است"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"صدا"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"لرزش"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fi/strings.xml b/packages/SoundPicker/res/values-fi/strings.xml
index 15bf65876023..fcda098b6aa6 100644
--- a/packages/SoundPicker/res/values-fi/strings.xml
+++ b/packages/SoundPicker/res/values-fi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Muokatun soittoäänen lisääminen epäonnistui."</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Muokatun soittoäänen poistaminen epäonnistui."</string>
<string name="app_label" msgid="3091611356093417332">"Äänet"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista on tyhjä"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ääni"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Värinä"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fr-rCA/strings.xml b/packages/SoundPicker/res/values-fr-rCA/strings.xml
index 1cc170fb6bbc..4d4545fd598c 100644
--- a/packages/SoundPicker/res/values-fr-rCA/strings.xml
+++ b/packages/SoundPicker/res/values-fr-rCA/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-fr/strings.xml b/packages/SoundPicker/res/values-fr/strings.xml
index ade2c16b8d01..9452e70ff8df 100644
--- a/packages/SoundPicker/res/values-fr/strings.xml
+++ b/packages/SoundPicker/res/values-fr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossible d\'ajouter une sonnerie personnalisée"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossible de supprimer la sonnerie personnalisée"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"La liste est vide"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-gl/strings.xml b/packages/SoundPicker/res/values-gl/strings.xml
index 83f2b2e9da5c..59a9d066a7aa 100644
--- a/packages/SoundPicker/res/values-gl/strings.xml
+++ b/packages/SoundPicker/res/values-gl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Non se pode engadir un ton de chamada personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Non se pode eliminar un ton de chamada personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está baleira"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Son"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibración"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-gu/strings.xml b/packages/SoundPicker/res/values-gu/strings.xml
index 8207512e0b64..209769fd7f57 100644
--- a/packages/SoundPicker/res/values-gu/strings.xml
+++ b/packages/SoundPicker/res/values-gu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"કસ્ટમ રિંગટોન ઉમેરવામાં અસમર્થ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"કસ્ટમ રિંગટોન કાઢી નાખવામાં અસમર્થ"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"સૂચિ ખાલી છે"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"સાઉન્ડ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"વાઇબ્રેશન"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hi/strings.xml b/packages/SoundPicker/res/values-hi/strings.xml
index 304201f941bf..ab3b7f802b0f 100644
--- a/packages/SoundPicker/res/values-hi/strings.xml
+++ b/packages/SoundPicker/res/values-hi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"आपके मुताबिक रिंगटोन नहीं जोड़ी जा सकी"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आपके मुताबिक रिंगटोन नहीं हटाई जा सकी"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"यह सूची खाली है"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"साउंड"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"वाइब्रेशन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hr/strings.xml b/packages/SoundPicker/res/values-hr/strings.xml
index 642c7d51a740..3adc5009448b 100644
--- a/packages/SoundPicker/res/values-hr/strings.xml
+++ b/packages/SoundPicker/res/values-hr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Dodavanje prilagođene melodije zvona nije moguće"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Brisanje prilagođene melodije zvona nije moguće"</string>
<string name="app_label" msgid="3091611356093417332">"Zvukovi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Popis je prazan"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibracija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hu/strings.xml b/packages/SoundPicker/res/values-hu/strings.xml
index 401da84b293f..32d4ba90eb9a 100644
--- a/packages/SoundPicker/res/values-hu/strings.xml
+++ b/packages/SoundPicker/res/values-hu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nem sikerült hozzáadni az egyéni csengőhangot"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nem sikerült törölni az egyéni csengőhangot"</string>
<string name="app_label" msgid="3091611356093417332">"Hangok"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista üres"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Hang"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Rezgés"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-hy/strings.xml b/packages/SoundPicker/res/values-hy/strings.xml
index d57345cd0423..da8934f72265 100644
--- a/packages/SoundPicker/res/values-hy/strings.xml
+++ b/packages/SoundPicker/res/values-hy/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Հնարավոր չէ հատուկ զանգերանգ ավելացնել"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Հնարավոր չէ ջնջել հատուկ զանգերանգը"</string>
<string name="app_label" msgid="3091611356093417332">"Ձայներ"</string>
- <string name="empty_list" msgid="2871978423955821191">"Ցանկը դատարկ է"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ձայն"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Թրթռոց"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-in/strings.xml b/packages/SoundPicker/res/values-in/strings.xml
index 518a09da863b..86dce643de37 100644
--- a/packages/SoundPicker/res/values-in/strings.xml
+++ b/packages/SoundPicker/res/values-in/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambahkan nada dering khusus"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat menghapus nada dering khusus"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"Daftar ini kosong"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Suara"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-is/strings.xml b/packages/SoundPicker/res/values-is/strings.xml
index 7f05c7979858..d0fce78ff9d9 100644
--- a/packages/SoundPicker/res/values-is/strings.xml
+++ b/packages/SoundPicker/res/values-is/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Get ekki bætt sérsniðnum hringitóni við"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Get ekki eytt sérsniðnum hringitóni"</string>
<string name="app_label" msgid="3091611356093417332">"Hljóð"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listinn er auður"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Hljóð"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Titringur"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-it/strings.xml b/packages/SoundPicker/res/values-it/strings.xml
index e1d8e19a53ea..632cb41f40f9 100644
--- a/packages/SoundPicker/res/values-it/strings.xml
+++ b/packages/SoundPicker/res/values-it/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Impossibile aggiungere suoneria personalizzata"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Impossibile eliminare suoneria personalizzata"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"L\'elenco è vuoto"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Suoneria"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrazione"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-iw/strings.xml b/packages/SoundPicker/res/values-iw/strings.xml
index 238370c9201f..387b1409c3ce 100644
--- a/packages/SoundPicker/res/values-iw/strings.xml
+++ b/packages/SoundPicker/res/values-iw/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"לא ניתן להוסיף רינגטון מותאם אישית"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"לא ניתן למחוק רינגטון מותאם אישית"</string>
<string name="app_label" msgid="3091611356093417332">"צלילים"</string>
- <string name="empty_list" msgid="2871978423955821191">"הרשימה ריקה"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"צליל"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"רטט"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ja/strings.xml b/packages/SoundPicker/res/values-ja/strings.xml
index 408e87007088..7c2aec60df75 100644
--- a/packages/SoundPicker/res/values-ja/strings.xml
+++ b/packages/SoundPicker/res/values-ja/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"カスタム着信音を追加できません"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"カスタム着信音を削除できません"</string>
<string name="app_label" msgid="3091611356093417332">"サウンド"</string>
- <string name="empty_list" msgid="2871978423955821191">"このリストは空です"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"バイブレーション"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ka/strings.xml b/packages/SoundPicker/res/values-ka/strings.xml
index 83dfc3c6e553..1cfe2401a99b 100644
--- a/packages/SoundPicker/res/values-ka/strings.xml
+++ b/packages/SoundPicker/res/values-ka/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"მორგებული ზარის დამატება შეუძლებელია"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"მორგებული ზარის წაშლა შეუძლებელია"</string>
<string name="app_label" msgid="3091611356093417332">"ხმები"</string>
- <string name="empty_list" msgid="2871978423955821191">"სია ცარიელია"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ხმა"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ვიბრაცია"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-kk/strings.xml b/packages/SoundPicker/res/values-kk/strings.xml
index 1815a95bc362..8c4c169bd65c 100644
--- a/packages/SoundPicker/res/values-kk/strings.xml
+++ b/packages/SoundPicker/res/values-kk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Арнаулы рингтонды енгізу мүмкін емес"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Арнаулы рингтонды жою мүмкін емес"</string>
<string name="app_label" msgid="3091611356093417332">"Дыбыстар"</string>
- <string name="empty_list" msgid="2871978423955821191">"Тізім бос."</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Дыбыс"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Діріл"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-km/strings.xml b/packages/SoundPicker/res/values-km/strings.xml
index 6ae048f50515..a334429e5f1f 100644
--- a/packages/SoundPicker/res/values-km/strings.xml
+++ b/packages/SoundPicker/res/values-km/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"មិន​អាច​បន្ថែម​សំឡេង​រោទ៍​ផ្ទាល់ខ្លួន​បាន"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"មិន​អាច​លុប​សំឡេង​រោទ៍​ផ្ទាល់ខ្លួន​បាន​ទេ"</string>
<string name="app_label" msgid="3091611356093417332">"សំឡេង"</string>
- <string name="empty_list" msgid="2871978423955821191">"បញ្ជីគឺទទេ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"សំឡេង"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ការញ័រ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-kn/strings.xml b/packages/SoundPicker/res/values-kn/strings.xml
index c528866cf735..da90ccbaca0b 100644
--- a/packages/SoundPicker/res/values-kn/strings.xml
+++ b/packages/SoundPicker/res/values-kn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ಕಸ್ಟಮ್ ರಿಂಗ್‌ಟೋನ್ ಸೇರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ಕಸ್ಟಮ್ ರಿಂಗ್‌ಟೋನ್ ಅಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="app_label" msgid="3091611356093417332">"ಧ್ವನಿಗಳು"</string>
- <string name="empty_list" msgid="2871978423955821191">"ಪಟ್ಟಿ ಖಾಲಿಯಾಗಿದೆ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ಧ್ವನಿ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ವೈಬ್ರೇಷನ್"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ko/strings.xml b/packages/SoundPicker/res/values-ko/strings.xml
index bcab6b21f9d6..70554d6e5a4d 100644
--- a/packages/SoundPicker/res/values-ko/strings.xml
+++ b/packages/SoundPicker/res/values-ko/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"맞춤 벨소리를 추가할 수 없습니다."</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"맞춤 벨소리를 삭제할 수 없습니다."</string>
<string name="app_label" msgid="3091611356093417332">"소리"</string>
- <string name="empty_list" msgid="2871978423955821191">"목록이 비어 있음"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"소리"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"진동"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ky/strings.xml b/packages/SoundPicker/res/values-ky/strings.xml
index babd8c0a3258..3c9522856027 100644
--- a/packages/SoundPicker/res/values-ky/strings.xml
+++ b/packages/SoundPicker/res/values-ky/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Жеке рингтон кошулбай жатат"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Жеке рингтон жок кылынбай жатат"</string>
<string name="app_label" msgid="3091611356093417332">"Үндөр"</string>
- <string name="empty_list" msgid="2871978423955821191">"Тизме бош"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Үн"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Дирилдөө"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lo/strings.xml b/packages/SoundPicker/res/values-lo/strings.xml
index 5e496e8f8e89..8bcae0df95d2 100644
--- a/packages/SoundPicker/res/values-lo/strings.xml
+++ b/packages/SoundPicker/res/values-lo/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Unable to add custom ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Unable to delete custom ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"ສຽງ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ລາຍຊື່ຫວ່າງເປົ່າ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ສຽງ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ການສັ່ນເຕືອນ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lt/strings.xml b/packages/SoundPicker/res/values-lt/strings.xml
index c68cc3bfefb1..c7ea3691a128 100644
--- a/packages/SoundPicker/res/values-lt/strings.xml
+++ b/packages/SoundPicker/res/values-lt/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepavyksta pridėti tinkinto skambėjimo tono"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepavyksta ištrinti tinkinto skambėjimo tono"</string>
<string name="app_label" msgid="3091611356093417332">"Garsai"</string>
- <string name="empty_list" msgid="2871978423955821191">"Sąrašas yra tuščias"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Garsas"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibravimas"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-lv/strings.xml b/packages/SoundPicker/res/values-lv/strings.xml
index fab7f8c91715..2a2628972e21 100644
--- a/packages/SoundPicker/res/values-lv/strings.xml
+++ b/packages/SoundPicker/res/values-lv/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nevar pievienot pielāgotu zvana signālu"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nevar izdzēst pielāgotu zvana signālu"</string>
<string name="app_label" msgid="3091611356093417332">"Skaņas"</string>
- <string name="empty_list" msgid="2871978423955821191">"Saraksts ir tukšs"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Skaņa"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrācija"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mk/strings.xml b/packages/SoundPicker/res/values-mk/strings.xml
index 4f2322ae2350..545d5ed2f8f2 100644
--- a/packages/SoundPicker/res/values-mk/strings.xml
+++ b/packages/SoundPicker/res/values-mk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не може да се додаде приспособена мелодија"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не може да се избрише приспособена мелодија"</string>
<string name="app_label" msgid="3091611356093417332">"Звуци"</string>
- <string name="empty_list" msgid="2871978423955821191">"Списокот е празен"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрации"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ml/strings.xml b/packages/SoundPicker/res/values-ml/strings.xml
index 16cf725756cc..21da8e86b6c5 100644
--- a/packages/SoundPicker/res/values-ml/strings.xml
+++ b/packages/SoundPicker/res/values-ml/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ഇഷ്ടാനുസൃത റിംഗ്‌ടോൺ ചേർക്കാനാവില്ല"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ഇഷ്ടാനുസൃത റിംഗ്‌ടോൺ ഇല്ലാതാക്കാനാവില്ല"</string>
<string name="app_label" msgid="3091611356093417332">"ശബ്‌ദങ്ങൾ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ലിസ്‌റ്റിൽ ഒന്നുമില്ല"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ശബ്‌ദം"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"വൈബ്രേഷൻ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mn/strings.xml b/packages/SoundPicker/res/values-mn/strings.xml
index 6215a41a27d9..15f7d12b42ca 100644
--- a/packages/SoundPicker/res/values-mn/strings.xml
+++ b/packages/SoundPicker/res/values-mn/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Захиалгат хонхны ая нэмэх боломжгүй"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Захиалгат хонхны ая устгах боломжгүй"</string>
<string name="app_label" msgid="3091611356093417332">"Дуу чимээ"</string>
- <string name="empty_list" msgid="2871978423955821191">"Жагсаалт хоосон байна"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Дуу"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Чичиргээ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-mr/strings.xml b/packages/SoundPicker/res/values-mr/strings.xml
index fe2fe127e231..3ddb99114e3a 100644
--- a/packages/SoundPicker/res/values-mr/strings.xml
+++ b/packages/SoundPicker/res/values-mr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"कस्टम रिंगटोन जोडण्यात अक्षम"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"कस्टम रिंगटोन हटविण्यात अक्षम"</string>
<string name="app_label" msgid="3091611356093417332">"आवाज"</string>
- <string name="empty_list" msgid="2871978423955821191">"ही सूची रिकामी आहे"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"आवाज"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"व्हायब्रेशन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ms/strings.xml b/packages/SoundPicker/res/values-ms/strings.xml
index 5788f18c0d15..9d87d72858ea 100644
--- a/packages/SoundPicker/res/values-ms/strings.xml
+++ b/packages/SoundPicker/res/values-ms/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tidak dapat menambah nada dering tersuai"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tidak dapat memadamkan nada dering tersuai"</string>
<string name="app_label" msgid="3091611356093417332">"Bunyi"</string>
- <string name="empty_list" msgid="2871978423955821191">"Senarai ini kosong"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Bunyi"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Getaran"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-my/strings.xml b/packages/SoundPicker/res/values-my/strings.xml
index 0a1a4cf64ed6..62163e950475 100644
--- a/packages/SoundPicker/res/values-my/strings.xml
+++ b/packages/SoundPicker/res/values-my/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ထည့်သွင်း၍မရပါ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"စိတ်ကြိုက်ဖုန်းမြည်သံကို ဖျက်၍မရပါ"</string>
<string name="app_label" msgid="3091611356093417332">"အသံများ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ဤစာရင်းတွင် မည်သည့်အရာမျှမရှိပါ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"အသံ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"တုန်ခါမှု"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-nb/strings.xml b/packages/SoundPicker/res/values-nb/strings.xml
index c315801e4d9c..e4e259af541d 100644
--- a/packages/SoundPicker/res/values-nb/strings.xml
+++ b/packages/SoundPicker/res/values-nb/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Kan ikke legge til egendefinert ringelyd"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Kan ikke slette egendefinert ringelyd"</string>
<string name="app_label" msgid="3091611356093417332">"Lyder"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listen er tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Lyd"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrering"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ne/strings.xml b/packages/SoundPicker/res/values-ne/strings.xml
index b95b70c30243..0a2bceb23e19 100644
--- a/packages/SoundPicker/res/values-ne/strings.xml
+++ b/packages/SoundPicker/res/values-ne/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"आफू अनुकूल रिङटोन थप्न सकिएन"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"आफू अनुकूल रिङटोनलाई मेट्न सकिएन"</string>
<string name="app_label" msgid="3091611356093417332">"ध्वनिहरू"</string>
- <string name="empty_list" msgid="2871978423955821191">"यो सूची खाली छ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ध्वनि"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"भाइब्रेसन"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-nl/strings.xml b/packages/SoundPicker/res/values-nl/strings.xml
index 192431b5a30a..5b6fb70b2a82 100644
--- a/packages/SoundPicker/res/values-nl/strings.xml
+++ b/packages/SoundPicker/res/values-nl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Toevoegen van aangepaste ringtone is mislukt"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Verwijderen van aangepaste ringtone is mislukt"</string>
<string name="app_label" msgid="3091611356093417332">"Geluiden"</string>
- <string name="empty_list" msgid="2871978423955821191">"De lijst is leeg"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Geluid"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Trillen"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-or/strings.xml b/packages/SoundPicker/res/values-or/strings.xml
index 5d82039dafec..45ce594c9ce3 100644
--- a/packages/SoundPicker/res/values-or/strings.xml
+++ b/packages/SoundPicker/res/values-or/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"କଷ୍ଟମ୍‍ ରିଙ୍ଗଟୋନ୍‍ ଯୋଡ଼ିପାରିବ ନାହିଁ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"କଷ୍ଟମ୍‍ ରିଙ୍ଗଟୋନ୍‍ ଡିଲିଟ୍‍ କରିପାରିବ ନାହିଁ"</string>
<string name="app_label" msgid="3091611356093417332">"ସାଉଣ୍ଡ"</string>
- <string name="empty_list" msgid="2871978423955821191">"ତାଲିକା ଖାଲି ଅଛି"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ସାଉଣ୍ଡ"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ଭାଇବ୍ରେସନ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pa/strings.xml b/packages/SoundPicker/res/values-pa/strings.xml
index 63ecb9d9175c..1e62f64a65ea 100644
--- a/packages/SoundPicker/res/values-pa/strings.xml
+++ b/packages/SoundPicker/res/values-pa/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਸ਼ਾਮਲ ਕਰਨ ਦੇ ਅਯੋਗ"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ਵਿਉਂਤੀ ਰਿੰਗਟੋਨ ਨੂੰ ਮਿਟਾਉਣ ਦੇ ਅਯੋਗ"</string>
<string name="app_label" msgid="3091611356093417332">"Sounds"</string>
- <string name="empty_list" msgid="2871978423955821191">"ਇਹ ਸੂਚੀ ਖਾਲੀ ਹੈ"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ਅਵਾਜ਼"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"ਥਰਥਰਾਹਟ"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pl/strings.xml b/packages/SoundPicker/res/values-pl/strings.xml
index 310f40f6a217..1b3b5c420048 100644
--- a/packages/SoundPicker/res/values-pl/strings.xml
+++ b/packages/SoundPicker/res/values-pl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nie można dodać dzwonka niestandardowego"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nie można usunąć dzwonka niestandardowego"</string>
<string name="app_label" msgid="3091611356093417332">"Dźwięki"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista jest pusta"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Dźwięk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Wibracje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt-rBR/strings.xml b/packages/SoundPicker/res/values-pt-rBR/strings.xml
index 9f58ca0e6125..7b545e15ccaa 100644
--- a/packages/SoundPicker/res/values-pt-rBR/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rBR/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt-rPT/strings.xml b/packages/SoundPicker/res/values-pt-rPT/strings.xml
index fd4e69fcc35b..5d742f1d75c7 100644
--- a/packages/SoundPicker/res/values-pt-rPT/strings.xml
+++ b/packages/SoundPicker/res/values-pt-rPT/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível eliminar o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-pt/strings.xml b/packages/SoundPicker/res/values-pt/strings.xml
index 9f58ca0e6125..7b545e15ccaa 100644
--- a/packages/SoundPicker/res/values-pt/strings.xml
+++ b/packages/SoundPicker/res/values-pt/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Não foi possível adicionar o toque personalizado"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Não foi possível excluir o toque personalizado"</string>
<string name="app_label" msgid="3091611356093417332">"Sons"</string>
- <string name="empty_list" msgid="2871978423955821191">"A lista está vazia"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Som"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibração"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ro/strings.xml b/packages/SoundPicker/res/values-ro/strings.xml
index 08d937cd51b5..58b5aeb4dca8 100644
--- a/packages/SoundPicker/res/values-ro/strings.xml
+++ b/packages/SoundPicker/res/values-ro/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nu se poate adăuga tonul de sonerie personalizat"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nu se poate șterge tonul de sonerie personalizat"</string>
<string name="app_label" msgid="3091611356093417332">"Sunete"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista este goală"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sunet"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrație"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ru/strings.xml b/packages/SoundPicker/res/values-ru/strings.xml
index be5495a0eab4..0d48ac1e8785 100644
--- a/packages/SoundPicker/res/values-ru/strings.xml
+++ b/packages/SoundPicker/res/values-ru/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не удалось добавить рингтон"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не удалось удалить рингтон"</string>
<string name="app_label" msgid="3091611356093417332">"Звуки"</string>
- <string name="empty_list" msgid="2871978423955821191">"Список пуст"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрация"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-si/strings.xml b/packages/SoundPicker/res/values-si/strings.xml
index 6ba86cb98ee6..1872b6b27f30 100644
--- a/packages/SoundPicker/res/values-si/strings.xml
+++ b/packages/SoundPicker/res/values-si/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"අභිරුචි නාද රිද්මය එක් කළ නොහැකිය"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"අභිරුචි නාද රිද්මය මැකිය නොහැකිය"</string>
<string name="app_label" msgid="3091611356093417332">"ශබ්ද"</string>
- <string name="empty_list" msgid="2871978423955821191">"ලැයිස්තුව හිස් ය"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ශබ්දය"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"කම්පනය"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sk/strings.xml b/packages/SoundPicker/res/values-sk/strings.xml
index 8f54350ecc34..8ff6d12e3817 100644
--- a/packages/SoundPicker/res/values-sk/strings.xml
+++ b/packages/SoundPicker/res/values-sk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nepodarilo sa pridať vlastný tón zvonenia"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nepodarilo sa odstrániť vlastný tón zvonenia"</string>
<string name="app_label" msgid="3091611356093417332">"Zvuky"</string>
- <string name="empty_list" msgid="2871978423955821191">"Zoznam je prázdny"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvuk"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibrácie"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sl/strings.xml b/packages/SoundPicker/res/values-sl/strings.xml
index 1a818b23fcb7..77a2a2cef0e7 100644
--- a/packages/SoundPicker/res/values-sl/strings.xml
+++ b/packages/SoundPicker/res/values-sl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Tona zvonjenja po meri ni mogoče dodati"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Tona zvonjenja po meri ni mogoče izbrisati"</string>
<string name="app_label" msgid="3091611356093417332">"Zvoki"</string>
- <string name="empty_list" msgid="2871978423955821191">"Seznam je prazen"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Zvok"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibriranje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sq/strings.xml b/packages/SoundPicker/res/values-sq/strings.xml
index 4c497bb76cd5..e35dd71c3beb 100644
--- a/packages/SoundPicker/res/values-sq/strings.xml
+++ b/packages/SoundPicker/res/values-sq/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Nuk mund të shtojë ton zileje të personalizuar"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Nuk mund të fshijë ton zileje të personalizuar"</string>
<string name="app_label" msgid="3091611356093417332">"Tingujt"</string>
- <string name="empty_list" msgid="2871978423955821191">"Lista është bosh"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Me tingull"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Me dridhje"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sr/strings.xml b/packages/SoundPicker/res/values-sr/strings.xml
index 2ec1f178aa5b..bc573f5a2662 100644
--- a/packages/SoundPicker/res/values-sr/strings.xml
+++ b/packages/SoundPicker/res/values-sr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Додавање прилагођене мелодије звона није успело"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Брисање прилагођене мелодије звона није успело"</string>
<string name="app_label" msgid="3091611356093417332">"Звукови"</string>
- <string name="empty_list" msgid="2871978423955821191">"Листа је празна"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вибрирање"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sv/strings.xml b/packages/SoundPicker/res/values-sv/strings.xml
index cb1cd3aeee98..c1dd1c24d52b 100644
--- a/packages/SoundPicker/res/values-sv/strings.xml
+++ b/packages/SoundPicker/res/values-sv/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Det gick inte att lägga till en egen ringsignal"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Det gick inte att radera den egna ringsignalen"</string>
<string name="app_label" msgid="3091611356093417332">"Ljud"</string>
- <string name="empty_list" msgid="2871978423955821191">"Listan är tom"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ljud"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Vibration"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-sw/strings.xml b/packages/SoundPicker/res/values-sw/strings.xml
index 8fd6d582f99f..b0234500b64c 100644
--- a/packages/SoundPicker/res/values-sw/strings.xml
+++ b/packages/SoundPicker/res/values-sw/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Imeshindwa kuongeza mlio maalum wa simu"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Imeshindwa kufuta mlio maalum wa simu"</string>
<string name="app_label" msgid="3091611356093417332">"Sauti"</string>
- <string name="empty_list" msgid="2871978423955821191">"Orodha hii haina chochote"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Sauti"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Mtetemo"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ta/strings.xml b/packages/SoundPicker/res/values-ta/strings.xml
index 692e58a5d8a3..38e45b705d22 100644
--- a/packages/SoundPicker/res/values-ta/strings.xml
+++ b/packages/SoundPicker/res/values-ta/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"பிரத்தியேக ரிங்டோனைச் சேர்க்க முடியவில்லை"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"பிரத்தியேக ரிங்டோனை நீக்க முடியவில்லை"</string>
<string name="app_label" msgid="3091611356093417332">"ஒலிகள்"</string>
- <string name="empty_list" msgid="2871978423955821191">"பட்டியல் காலியாக உள்ளது"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"ஒலி"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"அதிர்வு"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-te/strings.xml b/packages/SoundPicker/res/values-te/strings.xml
index ce13e5357b00..2d03ac0e844c 100644
--- a/packages/SoundPicker/res/values-te/strings.xml
+++ b/packages/SoundPicker/res/values-te/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"అనుకూల రింగ్‌టోన్‌ను జోడించలేకపోయింది"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"అనుకూల రింగ్‌టోన్‌ను తొలగించలేకపోయింది"</string>
<string name="app_label" msgid="3091611356093417332">"ధ్వనులు"</string>
- <string name="empty_list" msgid="2871978423955821191">"మీ లిస్ట్ ఖాళీగా ఉంది"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"సౌండ్"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"వైబ్రేషన్"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-th/strings.xml b/packages/SoundPicker/res/values-th/strings.xml
index d5dc9b71b37a..cc2e43f98024 100644
--- a/packages/SoundPicker/res/values-th/strings.xml
+++ b/packages/SoundPicker/res/values-th/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"ไม่สามารถเพิ่มเสียงเรียกเข้าที่กำหนดเอง"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"ไม่สามารถลบเสียงเรียกเข้าที่กำหนดเอง"</string>
<string name="app_label" msgid="3091611356093417332">"เสียง"</string>
- <string name="empty_list" msgid="2871978423955821191">"รายการว่างเปล่า"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"เสียง"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"การสั่น"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-tl/strings.xml b/packages/SoundPicker/res/values-tl/strings.xml
index 4e3bf7e1749b..c0c17128a53f 100644
--- a/packages/SoundPicker/res/values-tl/strings.xml
+++ b/packages/SoundPicker/res/values-tl/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Hindi maidagdag ang custom na ringtone"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Hindi ma-delete ang custom na ringtone"</string>
<string name="app_label" msgid="3091611356093417332">"Mga Tunog"</string>
- <string name="empty_list" msgid="2871978423955821191">"Walang laman ang listahan"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Tunog"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Pag-vibrate"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-tr/strings.xml b/packages/SoundPicker/res/values-tr/strings.xml
index 51ac54168726..955c23f068bf 100644
--- a/packages/SoundPicker/res/values-tr/strings.xml
+++ b/packages/SoundPicker/res/values-tr/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Özel zil sesi eklenemiyor"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Özel zil sesi silinemiyor"</string>
<string name="app_label" msgid="3091611356093417332">"Sesler"</string>
- <string name="empty_list" msgid="2871978423955821191">"Liste boş"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Ses"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Titreşim"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-uk/strings.xml b/packages/SoundPicker/res/values-uk/strings.xml
index a905b952a736..42dbfb0ec494 100644
--- a/packages/SoundPicker/res/values-uk/strings.xml
+++ b/packages/SoundPicker/res/values-uk/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Не вдалося додати користувацький сигнал дзвінка"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Не вдалося видалити користувацький сигнал дзвінка"</string>
<string name="app_label" msgid="3091611356093417332">"Звуки"</string>
- <string name="empty_list" msgid="2871978423955821191">"Список пустий"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Звук"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Вібрація"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-ur/strings.xml b/packages/SoundPicker/res/values-ur/strings.xml
index 9cae118de2c8..58141d6e8aca 100644
--- a/packages/SoundPicker/res/values-ur/strings.xml
+++ b/packages/SoundPicker/res/values-ur/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"حسب ضرورت رنگ ٹون شامل کرنے سے قاصر ہے"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"حسب ضرورت رنگ ٹون حذف کرنے سے قاصر ہے"</string>
<string name="app_label" msgid="3091611356093417332">"آوازیں"</string>
- <string name="empty_list" msgid="2871978423955821191">"فہرست خالی ہے"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"آواز"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"وائبریشن"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-uz/strings.xml b/packages/SoundPicker/res/values-uz/strings.xml
index e6231f23de69..c39db5f8dd01 100644
--- a/packages/SoundPicker/res/values-uz/strings.xml
+++ b/packages/SoundPicker/res/values-uz/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Maxsus rington qo‘shib bo‘lmadi"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Maxsus ringtonni o‘chirib bo‘lmadi"</string>
<string name="app_label" msgid="3091611356093417332">"Tovushlar"</string>
- <string name="empty_list" msgid="2871978423955821191">"Roʻyxat boʻsh"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Tovush"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Tebranish"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-vi/strings.xml b/packages/SoundPicker/res/values-vi/strings.xml
index 070ac160f7f7..bed0e9687271 100644
--- a/packages/SoundPicker/res/values-vi/strings.xml
+++ b/packages/SoundPicker/res/values-vi/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Không thể thêm nhạc chuông tùy chỉnh"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Không thể xóa nhạc chuông tùy chỉnh"</string>
<string name="app_label" msgid="3091611356093417332">"Âm thanh"</string>
- <string name="empty_list" msgid="2871978423955821191">"Danh sách này đang trống"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Âm thanh"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Rung"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rCN/strings.xml b/packages/SoundPicker/res/values-zh-rCN/strings.xml
index 0420fc9abf85..864aaae51264 100644
--- a/packages/SoundPicker/res/values-zh-rCN/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rCN/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"无法添加自定义铃声"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"无法删除自定义铃声"</string>
<string name="app_label" msgid="3091611356093417332">"声音"</string>
- <string name="empty_list" msgid="2871978423955821191">"列表为空"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"声音"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"振动"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rHK/strings.xml b/packages/SoundPicker/res/values-zh-rHK/strings.xml
index 60d9a52e100b..4cde32d77a7b 100644
--- a/packages/SoundPicker/res/values-zh-rHK/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rHK/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法加入自訂鈴聲"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
<string name="app_label" msgid="3091611356093417332">"音效"</string>
- <string name="empty_list" msgid="2871978423955821191">"清單中沒有內容"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zh-rTW/strings.xml b/packages/SoundPicker/res/values-zh-rTW/strings.xml
index c173c0a3c1ae..df8a66ac382e 100644
--- a/packages/SoundPicker/res/values-zh-rTW/strings.xml
+++ b/packages/SoundPicker/res/values-zh-rTW/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"無法新增自訂鈴聲"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"無法刪除自訂鈴聲"</string>
<string name="app_label" msgid="3091611356093417332">"音效"</string>
- <string name="empty_list" msgid="2871978423955821191">"清單中沒有任何項目"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"音效"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"震動"</string>
</resources>
diff --git a/packages/SoundPicker/res/values-zu/strings.xml b/packages/SoundPicker/res/values-zu/strings.xml
index 978e925d82a0..29a8ffe846b2 100644
--- a/packages/SoundPicker/res/values-zu/strings.xml
+++ b/packages/SoundPicker/res/values-zu/strings.xml
@@ -26,7 +26,4 @@
<string name="unable_to_add_ringtone" msgid="4583511263449467326">"Ayikwazi ukwengeza ithoni yokukhala yangokwezifiso"</string>
<string name="unable_to_delete_ringtone" msgid="6792301380142859496">"Ayikwazi ukususa ithoni yokukhala yangokwezifiso"</string>
<string name="app_label" msgid="3091611356093417332">"Imisindo"</string>
- <string name="empty_list" msgid="2871978423955821191">"Uhlu alunalutho"</string>
- <string name="sound_page_title" msgid="2143312098775103522">"Umsindo"</string>
- <string name="vibration_page_title" msgid="6519501440349124677">"Ukudlidliza"</string>
</resources>
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 9d3200dc340d..e6a82e83433b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -214,6 +214,7 @@ android_library {
lint: {
extra_check_modules: ["SystemUILintChecker"],
+ warning_checks: ["MissingApacheLicenseDetector"],
},
}
@@ -254,7 +255,6 @@ filegroup {
srcs: [
/* Keyguard converted tests */
// data
- "tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
"tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
@@ -402,10 +402,19 @@ filegroup {
"tests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt",
"tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt",
"tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt",
+
],
path: "tests/src",
}
+filegroup {
+ name: "SystemUI-tests-multivalent",
+ srcs: [
+ "multivalentTests/src/**/*.kt",
+ ],
+ path: "multivalentTests/src",
+}
+
java_library {
name: "SystemUI-tests-concurrency",
srcs: [
@@ -494,6 +503,7 @@ android_library {
"src/**/*.java",
"src/**/I*.aidl",
":ReleaseJavaFiles",
+ ":SystemUI-tests-multivalent",
":SystemUI-tests-utils",
],
static_libs: [
@@ -572,6 +582,7 @@ android_robolectric_test {
":SystemUI-tests-utils",
":SystemUI-test-fakes",
":SystemUI-tests-robolectric-pilots",
+ ":SystemUI-tests-multivalent",
],
static_libs: [
"androidx.test.uiautomator_uiautomator",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 03f7c9968a1d..de73b77b21a3 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -32,22 +32,6 @@
]
},
{
- "name": "SystemUIGoogleScreenshotTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.Postsubmit"
- }
- ],
- // The test doesn't run on AOSP Cuttlefish
- "keywords": ["internal"]
- },
- {
// TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
"name": "SystemUIGoogleBiometricsScreenshotTests",
"options": [
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 0f55f35adc4e..eadcd7c27a18 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -1,15 +1,17 @@
package: "com.android.systemui.accessibility.accessibilitymenu"
-flag {
- name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
- namespace: "accessibility"
- description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
- bug: "298467628"
-}
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
flag {
name: "a11y_menu_hide_before_taking_action"
namespace: "accessibility"
description: "Hides the AccessibilityMenuService UI before taking action instead of after."
bug: "292020123"
-} \ No newline at end of file
+}
+
+flag {
+ name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
+ namespace: "accessibility"
+ description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
+ bug: "298467628"
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
index e06787fdd297..66e37a13078a 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hy/strings.xml
@@ -10,7 +10,7 @@
<string name="power_utterance" msgid="7444296686402104807">"Սնուցման կոճակի ընտրանքներ"</string>
<string name="recent_apps_label" msgid="6583276995616385847">"Վերջին հավելվածներ"</string>
<string name="lockscreen_label" msgid="648347953557887087">"Կողպէկրան"</string>
- <string name="quick_settings_label" msgid="2999117381487601865">"Արագ\\nկարգավորումներ"</string>
+ <string name="quick_settings_label" msgid="2999117381487601865">"Արագ կարգա­վորումներ"</string>
<string name="notifications_label" msgid="6829741046963013567">"Ծանուցումներ"</string>
<string name="screenshot_label" msgid="863978141223970162">"Սքրինշոթ"</string>
<string name="screenshot_utterance" msgid="1430760563401895074">"Ստանալ սքրինշոթը"</string>
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 8841967b1535..bcf1535b94fa 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -1,5 +1,7 @@
package: "com.android.systemui"
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
flag {
name: "floating_menu_overlaps_nav_bars_flag"
namespace: "accessibility"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 211af908a877..05675289db64 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -31,6 +31,13 @@ flag {
}
flag {
+ name: "notification_async_hybrid_view_inflation"
+ namespace: "systemui"
+ description: "Inflates the hybrid (single-line) notification views form the background thread."
+ bug: "217799515"
+}
+
+flag {
name: "scene_container"
namespace: "systemui"
description: "Enables the scene container framework go/flexiglass."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 4ea57a8cc007..ab4db451406d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -290,9 +290,10 @@ class ActivityLaunchAnimator(
controller: Controller?,
animate: Boolean = true,
packageName: String? = null,
+ showOverLockscreen: Boolean = false,
intentStarter: PendingIntentStarter
) {
- startIntentWithAnimation(controller, animate, packageName) {
+ startIntentWithAnimation(controller, animate, packageName, showOverLockscreen) {
intentStarter.startPendingIntent(it)
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 6c4b695ed709..af35ea44322f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -68,6 +68,7 @@ constructor(
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
get() = launchContainer.overlay
+
private val launchContainerLocation = IntArray(2)
/** The ghost view that is drawn and animated instead of the ghosted view. */
@@ -206,9 +207,8 @@ constructor(
return
}
- backgroundView = FrameLayout(launchContainer.context).also {
- launchContainerOverlay.add(it)
- }
+ backgroundView =
+ FrameLayout(launchContainer.context).also { launchContainerOverlay.add(it) }
// We wrap the ghosted view background and use it to draw the expandable background. Its
// alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -226,6 +226,17 @@ constructor(
// the content before fading out the background.
ghostView = GhostView.addGhost(ghostedView, launchContainer)
+ // [GhostView.addGhost], the result of which is our [ghostView], creates a [GhostView], and
+ // adds it first to a [FrameLayout] container. It then adds _that_ container to an
+ // [OverlayViewGroup]. We need to turn off clipping for that container view. Currently,
+ // however, the only way to get a reference to that overlay is by going through our
+ // [ghostView]. The [OverlayViewGroup] will always be its grandparent view.
+ // TODO(b/306652954) reference the overlay view group directly if we can
+ (ghostView?.parent?.parent as? ViewGroup)?.let {
+ it.clipChildren = false
+ it.clipToPadding = false
+ }
+
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
new file mode 100644
index 000000000000..46125bee1523
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/MissingApacheLicenseDetector.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.systemui.lint
+
+import com.android.ide.common.blame.SourcePosition
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Location
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.time.Year
+import org.jetbrains.uast.UComment
+import org.jetbrains.uast.UFile
+
+/**
+ * Checks if every AOSP Java/Kotlin source code file is starting with Apache license information.
+ */
+class MissingApacheLicenseDetector : Detector(), SourceCodeScanner {
+
+ override fun getApplicableUastTypes() = listOf(UFile::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler? {
+ return object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ val firstComment = node.allCommentsInFile.firstOrNull()
+ // Normally we don't need to explicitly handle suppressing case and just return
+ // error as usual with indicating node and lint will ignore it for us. But here
+ // suppressing will be applied on top of comment that doesn't exist so we don't have
+ // node to return - it's a bit of corner case
+ if (firstComment != null && firstComment.isSuppressingComment()) {
+ return
+ }
+ if (firstComment == null || !firstComment.isLicenseComment()) {
+ val firstLineOfFile =
+ Location.create(
+ context.file,
+ SourcePosition(/* lineNumber= */ 1, /* column= */ 1, /* offset= */ 0)
+ )
+ context.report(
+ issue = ISSUE,
+ location = firstLineOfFile,
+ message =
+ "License header is missing\n" +
+ "Please add the following copyright and license header to the" +
+ " beginning of the file:\n\n" +
+ copyrightHeader
+ )
+ }
+ }
+ }
+ }
+
+ private fun UComment.isSuppressingComment(): Boolean {
+ val suppressingComment =
+ "//noinspection ${MissingApacheLicenseDetector::class.java.simpleName}"
+ return text.contains(suppressingComment)
+ }
+
+ private fun UComment.isLicenseComment(): Boolean {
+ // We probably don't want to compare full copyright header in case there are some small
+ // discrepancies in already existing files, e.g. year. We could do regexp but it should be
+ // good enough if this detector deals with missing
+ // license header instead of incorrect license header
+ return text.contains("Apache License")
+ }
+
+ private val copyrightHeader: String
+ get() =
+ """
+ /*
+ * Copyright (C) ${Year.now().value} The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ """
+ .trimIndent()
+ .trim()
+
+ companion object {
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "MissingApacheLicenseDetector",
+ briefDescription = "File is missing Apache license information",
+ explanation =
+ """
+ Every source code file should have copyright and license information \
+ attached at the beginning.""",
+ category = Category.COMPLIANCE,
+ priority = 8,
+ // ignored by default and then explicitly overridden in SysUI's soong configuration
+ severity = Severity.IGNORE,
+ implementation =
+ Implementation(MissingApacheLicenseDetector::class.java, Scope.JAVA_FILE_SCOPE),
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 520c8882b428..e09aa421c68a 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -42,6 +42,7 @@ class SystemUIIssueRegistry : IssueRegistry() {
StaticSettingsProviderDetector.ISSUE,
DemotingTestWithoutBugDetector.ISSUE,
TestFunctionNameViolationDetector.ISSUE,
+ MissingApacheLicenseDetector.ISSUE,
)
override val api: Int
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
new file mode 100644
index 000000000000..78e133fda3d6
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/MissingApacheLicenseDetectorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import java.time.Year
+import org.junit.Test
+
+class MissingApacheLicenseDetectorTest : SystemUILintDetectorTest() {
+ override fun getDetector(): Detector {
+ return MissingApacheLicenseDetector()
+ }
+
+ override fun getIssues(): List<Issue> {
+ return listOf(
+ MissingApacheLicenseDetector.ISSUE,
+ )
+ }
+
+ @Test
+ fun testHasCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ /*
+ * Copyright (C) ${Year.now().value} The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testDoesntHaveCopyright() {
+ lint()
+ .files(
+ kotlin(
+ """
+ package test.pkg.name
+
+ class MyTest
+ """
+ .trimIndent()
+ )
+ )
+ // skipping mode SUPPRESSIBLE because lint tries to add @Suppress to class which
+ // probably doesn't make much sense for license header (which is far above it) and for
+ // kotlin files that can have several classes. If someone really wants to omit header
+ // they can do it with //noinspection
+ .skipTestModes(TestMode.SUPPRESSIBLE)
+ .issues(MissingApacheLicenseDetector.ISSUE)
+ .run()
+ .expectContains("License header is missing")
+ }
+}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
index 4ed78b3b95ed..33024f764710 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.layout.ui.compose
+import android.util.SizeF
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -54,7 +55,14 @@ fun CommunalGridLayout(
Row(
modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
) {
- cardInfo.card.Content(Modifier.fillMaxSize())
+ cardInfo.card.Content(
+ modifier = Modifier.fillMaxSize(),
+ size =
+ SizeF(
+ layoutConfig.cardWidth.value,
+ layoutConfig.cardHeight(cardInfo.size).value,
+ ),
+ )
}
}
}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
index ac8aa67fa4bf..4b2a156c1dbd 100644
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.layout.ui.compose.config
+import android.util.SizeF
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -26,8 +27,11 @@ abstract class CommunalGridLayoutCard {
*
* To host non-Compose views, see
* https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
+ *
+ * @param size The size given to the card. Content of the card should fill all this space, given
+ * that margins and paddings have been taken care of by the layout.
*/
- @Composable abstract fun Content(modifier: Modifier)
+ @Composable abstract fun Content(modifier: Modifier, size: SizeF)
/**
* Sizes supported by the card.
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
index fdf65f5d5cc7..c1974caa5628 100644
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.communal.layout
+import android.util.SizeF
import androidx.compose.material3.Card
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -91,7 +92,7 @@ class CommunalLayoutEngineTest {
override val supportedSizes = listOf(size)
@Composable
- override fun Content(modifier: Modifier) {
+ override fun Content(modifier: Modifier, size: SizeF) {
Card(modifier = modifier, content = {})
}
}
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index c6e429a79e86..ddd1c67bd5fa 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -72,6 +72,10 @@ object ComposeFacade : BaseComposeFacade {
throwComposeUnavailableError()
}
+ override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+ throwComposeUnavailableError()
+ }
+
private fun throwComposeUnavailableError(): Nothing {
error(
"Compose is not available. Make sure to check isComposeAvailable() before calling any" +
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 1722685f4287..eeda6c63b68f 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -30,6 +30,7 @@ import com.android.compose.theme.PlatformTheme
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
+import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.people.ui.compose.PeopleScreen
@@ -104,6 +105,12 @@ object ComposeFacade : BaseComposeFacade {
}
}
+ override fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View {
+ return ComposeView(context).apply {
+ setContent { PlatformTheme { CommunalContainer(viewModel = viewModel) } }
+ }
+ }
+
// TODO(b/298525212): remove once Compose exposes window inset bounds.
private fun displayCutoutFromWindowInsets(
scope: CoroutineScope,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
new file mode 100644
index 000000000000..46d418a03c00
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -0,0 +1,123 @@
+package com.android.systemui.communal.ui.compose
+
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayout
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+
+object Scenes {
+ val Blank = SceneKey(name = "blank")
+ val Communal = SceneKey(name = "communal")
+}
+
+object Communal {
+ object Elements {
+ val Content = ElementKey("CommunalContent")
+ }
+}
+
+val sceneTransitions = transitions {
+ from(Scenes.Blank, to = Scenes.Communal) {
+ spec = tween(durationMillis = 500)
+
+ translate(Communal.Elements.Content, Edge.Right)
+ fade(Communal.Elements.Content)
+ }
+}
+
+/**
+ * View containing a [SceneTransitionLayout] that shows the communal UI and handles transitions.
+ *
+ * This is a temporary container to allow the communal UI to use [SceneTransitionLayout] for gesture
+ * handling and transitions before the full Flexiglass layout is ready.
+ */
+@Composable
+fun CommunalContainer(modifier: Modifier = Modifier, viewModel: CommunalViewModel) {
+ val (currentScene, setCurrentScene) = remember { mutableStateOf(Scenes.Blank) }
+
+ // Failsafe to hide the whole SceneTransitionLayout in case of bugginess.
+ var showSceneTransitionLayout by remember { mutableStateOf(true) }
+ if (!showSceneTransitionLayout) {
+ return
+ }
+
+ SceneTransitionLayout(
+ modifier = modifier.fillMaxSize(),
+ currentScene = currentScene,
+ onChangeScene = setCurrentScene,
+ transitions = sceneTransitions,
+ ) {
+ scene(Scenes.Blank, userActions = mapOf(Swipe.Left to Scenes.Communal)) {
+ BlankScene { showSceneTransitionLayout = false }
+ }
+
+ scene(
+ Scenes.Communal,
+ userActions = mapOf(Swipe.Right to Scenes.Blank),
+ ) {
+ CommunalScene(viewModel, modifier = modifier)
+ }
+ }
+}
+
+/**
+ * Blank scene that shows over keyguard/dream. This scene will eventually show nothing at all and is
+ * only used to allow for transitions to the communal scene.
+ */
+@Composable
+private fun BlankScene(
+ modifier: Modifier = Modifier,
+ hideSceneTransitionLayout: () -> Unit,
+) {
+ Box(modifier.fillMaxSize()) {
+ Column(
+ Modifier.fillMaxHeight()
+ .width(100.dp)
+ .align(Alignment.CenterEnd)
+ .background(Color(0x55e9f2eb)),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text("Default scene")
+
+ IconButton(onClick = hideSceneTransitionLayout) {
+ Icon(Icons.Filled.Close, contentDescription = "Close button")
+ }
+ }
+ }
+}
+
+/** Scene containing the glanceable hub UI. */
+@Composable
+private fun SceneScope.CommunalScene(
+ viewModel: CommunalViewModel,
+ modifier: Modifier = Modifier,
+) {
+ Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 3d827fb5c9a6..b8fb26406801 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -1,5 +1,8 @@
package com.android.systemui.communal.ui.compose
+import android.appwidget.AppWidgetHostView
+import android.os.Bundle
+import android.util.SizeF
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -12,9 +15,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.communal.layout.ui.compose.CommunalGridLayout
import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.res.R
@@ -24,6 +30,7 @@ fun CommunalHub(
viewModel: CommunalViewModel,
) {
val showTutorial by viewModel.showTutorialContent.collectAsState(initial = false)
+ val widgetContent by viewModel.widgetContent.collectAsState(initial = emptyList())
Box(
modifier = modifier.fillMaxSize().background(Color.White),
) {
@@ -36,7 +43,7 @@ fun CommunalHub(
gridHeight = dimensionResource(R.dimen.communal_grid_height),
gridColumnsPerCard = integerResource(R.integer.communal_grid_columns_per_card),
),
- communalCards = if (showTutorial) tutorialContent else emptyList(),
+ communalCards = if (showTutorial) tutorialContent else widgetContent.map(::contentCard),
)
}
}
@@ -58,8 +65,37 @@ private fun tutorialCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutC
override val supportedSizes = listOf(size)
@Composable
- override fun Content(modifier: Modifier) {
+ override fun Content(modifier: Modifier, size: SizeF) {
Card(modifier = modifier, content = {})
}
}
}
+
+private fun contentCard(model: CommunalContentUiModel): CommunalGridLayoutCard {
+ return object : CommunalGridLayoutCard() {
+ override val supportedSizes = listOf(convertToCardSize(model.size))
+ override val priority = model.priority
+
+ @Composable
+ override fun Content(modifier: Modifier, size: SizeF) {
+ AndroidView(
+ modifier = modifier,
+ factory = {
+ model.view.apply {
+ if (this is AppWidgetHostView) {
+ updateAppWidgetSize(Bundle(), listOf(size))
+ }
+ }
+ },
+ )
+ }
+ }
+}
+
+private fun convertToCardSize(size: CommunalContentSize): CommunalGridLayoutCard.Size {
+ return when (size) {
+ CommunalContentSize.FULL -> CommunalGridLayoutCard.Size.FULL
+ CommunalContentSize.HALF -> CommunalGridLayoutCard.Size.HALF
+ CommunalContentSize.THIRD -> CommunalGridLayoutCard.Size.THIRD
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
new file mode 100644
index 000000000000..d005413fcbcf
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -0,0 +1,20 @@
+package com.android.compose.animation.scene
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import kotlinx.coroutines.CoroutineScope
+
+interface GestureHandler {
+ val draggable: DraggableHandler
+ val nestedScroll: NestedScrollHandler
+}
+
+interface DraggableHandler {
+ suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset)
+ fun onDelta(pixels: Float)
+ suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float)
+}
+
+interface NestedScrollHandler {
+ val connection: NestedScrollConnection
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 3fd6828fca6b..9c799b282571 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
@@ -100,3 +101,19 @@ private class SceneScopeImpl(
MovableElement(layoutImpl, scene, key, modifier, content)
}
}
+
+/** The destination scene when swiping up or left from [upOrLeft]. */
+internal fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
+ return when (orientation) {
+ Orientation.Vertical -> userActions[Swipe.Up]
+ Orientation.Horizontal -> userActions[Swipe.Left]
+ }
+}
+
+/** The destination scene when swiping down or right from [downOrRight]. */
+internal fun Scene.downOrRight(orientation: Orientation): SceneKey? {
+ return when (orientation) {
+ Orientation.Vertical -> userActions[Swipe.Down]
+ Orientation.Horizontal -> userActions[Swipe.Right]
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 4952270cb5f2..a40b29991877 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.activity.compose.BackHandler
+import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
@@ -39,7 +40,8 @@ import androidx.compose.ui.unit.IntSize
import com.android.compose.ui.util.fastForEach
import kotlinx.coroutines.channels.Channel
-internal class SceneTransitionLayoutImpl(
+@VisibleForTesting
+class SceneTransitionLayoutImpl(
onChangeScene: (SceneKey) -> Unit,
builder: SceneTransitionLayoutScope.() -> Unit,
transitions: SceneTransitions,
@@ -60,7 +62,7 @@ internal class SceneTransitionLayoutImpl(
* The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have
* any scene configured or right before the first measure pass of the layout.
*/
- internal var size by mutableStateOf(IntSize.Zero)
+ @VisibleForTesting var size by mutableStateOf(IntSize.Zero)
init {
setScenes(builder)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 1cbfe3057ff0..2dc53ab8bf76 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -16,10 +16,11 @@
package com.android.compose.animation.scene
+import android.util.Log
+import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
-import androidx.compose.foundation.gestures.DraggableState
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
@@ -34,10 +35,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
-import com.android.compose.nestedscroll.PriorityPostNestedScrollConnection
+import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -51,417 +51,380 @@ internal fun Modifier.swipeToScene(
layoutImpl: SceneTransitionLayoutImpl,
orientation: Orientation,
): Modifier {
- val state = layoutImpl.state.transitionState
- val currentScene = layoutImpl.scene(state.currentScene)
- val transition = remember {
- // Note that the currentScene here does not matter, it's only used for initializing the
- // transition and will be replaced when a drag event starts.
- SwipeTransition(initialScene = currentScene)
- }
-
- val enabled = state == transition || currentScene.shouldEnableSwipes(orientation)
-
- // Immediately start the drag if this our [transition] is currently animating to a scene (i.e.
- // the user released their input pointer after swiping in this orientation) and the user can't
- // swipe in the other direction.
- val startDragImmediately =
- state == transition &&
- transition.isAnimatingOffset &&
- !currentScene.shouldEnableSwipes(orientation.opposite())
-
- // The velocity threshold at which the intent of the user is to swipe up or down. It is the same
- // as SwipeableV2Defaults.VelocityThreshold.
- val velocityThreshold = with(LocalDensity.current) { 125.dp.toPx() }
-
- // The positional threshold at which the intent of the user is to swipe to the next scene. It is
- // the same as SwipeableV2Defaults.PositionalThreshold.
- val positionalThreshold = with(LocalDensity.current) { 56.dp.toPx() }
-
- val draggableState = rememberDraggableState { delta ->
- onDrag(layoutImpl, transition, orientation, delta)
- }
-
- return nestedScroll(
- connection =
- rememberSwipeToSceneNestedScrollConnection(
- orientation = orientation,
- coroutineScope = rememberCoroutineScope(),
- draggableState = draggableState,
- transition = transition,
- layoutImpl = layoutImpl,
- velocityThreshold = velocityThreshold,
- positionalThreshold = positionalThreshold
- ),
+ val gestureHandler = rememberSceneGestureHandler(layoutImpl, orientation)
+
+ /** Whether swipe should be enabled in the given [orientation]. */
+ fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
+ upOrLeft(orientation) != null || downOrRight(orientation) != null
+
+ val currentScene = gestureHandler.currentScene
+ val canSwipe = currentScene.shouldEnableSwipes(orientation)
+ val canOppositeSwipe =
+ currentScene.shouldEnableSwipes(
+ when (orientation) {
+ Orientation.Vertical -> Orientation.Horizontal
+ Orientation.Horizontal -> Orientation.Vertical
+ }
)
+
+ return nestedScroll(connection = gestureHandler.nestedScroll.connection)
.draggable(
- state = draggableState,
+ state = rememberDraggableState(onDelta = gestureHandler.draggable::onDelta),
orientation = orientation,
- enabled = enabled,
- startDragImmediately = startDragImmediately,
- onDragStarted = { onDragStarted(layoutImpl, transition, orientation) },
- onDragStopped = { velocity ->
- onDragStopped(
- layoutImpl = layoutImpl,
- transition = transition,
- velocity = velocity,
- velocityThreshold = velocityThreshold,
- positionalThreshold = positionalThreshold,
- )
- },
+ enabled = gestureHandler.isDrivingTransition || canSwipe,
+ // Immediately start the drag if this our [transition] is currently animating to a scene
+ // (i.e. the user released their input pointer after swiping in this orientation) and
+ // the user can't swipe in the other direction.
+ startDragImmediately =
+ gestureHandler.isDrivingTransition &&
+ gestureHandler.isAnimatingOffset &&
+ !canOppositeSwipe,
+ onDragStarted = gestureHandler.draggable::onDragStarted,
+ onDragStopped = gestureHandler.draggable::onDragStopped,
)
}
-private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
- var _currentScene by mutableStateOf(initialScene)
- override val currentScene: SceneKey
- get() = _currentScene.key
-
- var _fromScene by mutableStateOf(initialScene)
- override val fromScene: SceneKey
- get() = _fromScene.key
-
- var _toScene by mutableStateOf(initialScene)
- override val toScene: SceneKey
- get() = _toScene.key
-
- override val progress: Float
- get() {
- val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
- if (distance == 0f) {
- // This can happen only if fromScene == toScene.
- error(
- "Transition.progress should be called only when Transition.fromScene != " +
- "Transition.toScene"
- )
- }
- return offset / distance
+@Composable
+private fun rememberSceneGestureHandler(
+ layoutImpl: SceneTransitionLayoutImpl,
+ orientation: Orientation,
+): SceneGestureHandler {
+ val coroutineScope = rememberCoroutineScope()
+
+ val gestureHandler =
+ remember(layoutImpl, orientation, coroutineScope) {
+ SceneGestureHandler(layoutImpl, orientation, coroutineScope)
}
- override val isUserInputDriven = true
+ // Make sure we reset the scroll connection when this handler is removed from composition
+ val connection = gestureHandler.nestedScroll.connection
+ DisposableEffect(connection) { onDispose { connection.reset() } }
- /** The current offset caused by the drag gesture. */
- var dragOffset by mutableFloatStateOf(0f)
+ return gestureHandler
+}
+
+@VisibleForTesting
+class SceneGestureHandler(
+ private val layoutImpl: SceneTransitionLayoutImpl,
+ internal val orientation: Orientation,
+ private val coroutineScope: CoroutineScope,
+) : GestureHandler {
+ override val draggable: DraggableHandler = SceneDraggableHandler(this)
+
+ override val nestedScroll: SceneNestedScrollHandler = SceneNestedScrollHandler(this)
+
+ private var transitionState
+ get() = layoutImpl.state.transitionState
+ set(value) {
+ layoutImpl.state.transitionState = value
+ }
/**
- * Whether the offset is animated (the user lifted their finger) or if it is driven by gesture.
+ * The transition controlled by this gesture handler. It will be set as the [transitionState] in
+ * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition.
+ *
+ * Note: the initialScene here does not matter, it's only used for initializing the transition
+ * and will be replaced when a drag event starts.
*/
- var isAnimatingOffset by mutableStateOf(false)
+ private val swipeTransition = SwipeTransition(initialScene = currentScene)
- /** The animatable used to animate the offset once the user lifted its finger. */
- val offsetAnimatable = Animatable(0f, visibilityThreshold = OffsetVisibilityThreshold)
+ internal val currentScene: Scene
+ get() = layoutImpl.scene(transitionState.currentScene)
- /** Job to check that there is at most one offset animation in progress. */
- private var offsetAnimationJob: Job? = null
+ @VisibleForTesting
+ val isDrivingTransition
+ get() = transitionState == swipeTransition
- /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
- fun startOffsetAnimation(job: () -> Job) {
- stopOffsetAnimation()
- offsetAnimationJob = job()
- }
-
- /** Stops any ongoing offset animation. */
- fun stopOffsetAnimation() {
- offsetAnimationJob?.cancel()
- }
+ @VisibleForTesting
+ var isAnimatingOffset
+ get() = swipeTransition.isAnimatingOffset
+ private set(value) {
+ swipeTransition.isAnimatingOffset = value
+ }
- /** The absolute distance between [fromScene] and [toScene]. */
- var absoluteDistance = 0f
+ internal val swipeTransitionToScene
+ get() = swipeTransition._toScene
/**
- * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is above
- * or to the left of [toScene].
+ * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
+ * as SwipeableV2Defaults.VelocityThreshold.
*/
- var _distance by mutableFloatStateOf(0f)
- val distance: Float
- get() = _distance
-}
-
-/** The destination scene when swiping up or left from [this@upOrLeft]. */
-private fun Scene.upOrLeft(orientation: Orientation): SceneKey? {
- return when (orientation) {
- Orientation.Vertical -> userActions[Swipe.Up]
- Orientation.Horizontal -> userActions[Swipe.Left]
- }
-}
+ @VisibleForTesting val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
-/** The destination scene when swiping down or right from [this@downOrRight]. */
-private fun Scene.downOrRight(orientation: Orientation): SceneKey? {
- return when (orientation) {
- Orientation.Vertical -> userActions[Swipe.Down]
- Orientation.Horizontal -> userActions[Swipe.Right]
- }
-}
+ /**
+ * The positional threshold at which the intent of the user is to swipe to the next scene. It is
+ * the same as SwipeableV2Defaults.PositionalThreshold.
+ */
+ private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() }
-/** Whether swipe should be enabled in the given [orientation]. */
-private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
- return upOrLeft(orientation) != null || downOrRight(orientation) != null
-}
+ internal var gestureWithPriority: Any? = null
-private fun Orientation.opposite(): Orientation {
- return when (this) {
- Orientation.Vertical -> Orientation.Horizontal
- Orientation.Horizontal -> Orientation.Vertical
- }
-}
+ internal fun onDragStarted() {
+ if (isDrivingTransition) {
+ // This [transition] was already driving the animation: simply take over it.
+ // Stop animating and start from where the current offset.
+ swipeTransition.stopOffsetAnimation()
+ return
+ }
-private fun onDragStarted(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- orientation: Orientation,
-) {
- if (layoutImpl.state.transitionState == transition) {
- // This [transition] was already driving the animation: simply take over it.
- if (transition.isAnimatingOffset) {
- // Stop animating and start from where the current offset. Setting the animation job to
- // `null` will effectively cancel the animation.
- transition.stopOffsetAnimation()
- transition.dragOffset = transition.offsetAnimatable.value
+ val transition = transitionState
+ if (transition is TransitionState.Transition) {
+ // TODO(b/290184746): Better handle interruptions here if state != idle.
+ Log.w(
+ TAG,
+ "start from TransitionState.Transition is not fully supported: from" +
+ " ${transition.fromScene} to ${transition.toScene} " +
+ "(progress ${transition.progress})"
+ )
}
- return
- }
+ val fromScene = currentScene
- // TODO(b/290184746): Better handle interruptions here if state != idle.
+ swipeTransition._currentScene = fromScene
+ swipeTransition._fromScene = fromScene
- val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+ // We don't know where we are transitioning to yet given that the drag just started, so set
+ // it to fromScene, which will effectively be treated the same as Idle(fromScene).
+ swipeTransition._toScene = fromScene
- transition._currentScene = fromScene
- transition._fromScene = fromScene
+ swipeTransition.stopOffsetAnimation()
+ swipeTransition.dragOffset = 0f
- // We don't know where we are transitioning to yet given that the drag just started, so set it
- // to fromScene, which will effectively be treated the same as Idle(fromScene).
- transition._toScene = fromScene
+ // Use the layout size in the swipe orientation for swipe distance.
+ // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances,
+ // we will also have to make sure that we correctly handle overscroll.
+ swipeTransition.absoluteDistance =
+ when (orientation) {
+ Orientation.Horizontal -> layoutImpl.size.width
+ Orientation.Vertical -> layoutImpl.size.height
+ }.toFloat()
- transition.stopOffsetAnimation()
- transition.dragOffset = 0f
+ if (swipeTransition.absoluteDistance > 0f) {
+ transitionState = swipeTransition
+ }
+ }
- // Use the layout size in the swipe orientation for swipe distance.
- // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
- // will also have to make sure that we correctly handle overscroll.
- transition.absoluteDistance =
- when (orientation) {
- Orientation.Horizontal -> layoutImpl.size.width
- Orientation.Vertical -> layoutImpl.size.height
- }.toFloat()
+ internal fun onDrag(delta: Float) {
+ if (delta == 0f) return
- if (transition.absoluteDistance > 0f) {
- layoutImpl.state.transitionState = transition
- }
-}
+ swipeTransition.dragOffset += delta
-private fun onDrag(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- orientation: Orientation,
- delta: Float,
-) {
- transition.dragOffset += delta
+ // First check transition.fromScene should be changed for the case where the user quickly
+ // swiped twice in a row to accelerate the transition and go from A => B then B => C really
+ // fast.
+ maybeHandleAcceleratedSwipe()
- // First check transition.fromScene should be changed for the case where the user quickly swiped
- // twice in a row to accelerate the transition and go from A => B then B => C really fast.
- maybeHandleAcceleratedSwipe(transition, orientation)
+ val offset = swipeTransition.dragOffset
+ val fromScene = swipeTransition._fromScene
- val offset = transition.dragOffset
- val fromScene = transition._fromScene
+ // Compute the target scene depending on the current offset.
+ val target = fromScene.findTargetSceneAndDistance(offset)
- // Compute the target scene depending on the current offset.
- val target = fromScene.findTargetSceneAndDistance(orientation, offset, layoutImpl)
+ if (swipeTransition._toScene.key != target.sceneKey) {
+ swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
+ }
- if (transition._toScene.key != target.sceneKey) {
- transition._toScene = layoutImpl.scenes.getValue(target.sceneKey)
+ if (swipeTransition._distance != target.distance) {
+ swipeTransition._distance = target.distance
+ }
}
- if (transition._distance != target.distance) {
- transition._distance = target.distance
- }
-}
+ /**
+ * Change fromScene in the case where the user quickly swiped multiple times in the same
+ * direction to accelerate the transition from A => B then B => C.
+ */
+ private fun maybeHandleAcceleratedSwipe() {
+ val toScene = swipeTransition._toScene
+ val fromScene = swipeTransition._fromScene
-/**
- * Change fromScene in the case where the user quickly swiped multiple times in the same direction
- * to accelerate the transition from A => B then B => C.
- */
-private fun maybeHandleAcceleratedSwipe(
- transition: SwipeTransition,
- orientation: Orientation,
-) {
- val toScene = transition._toScene
- val fromScene = transition._fromScene
+ // If the swipe was not committed, don't do anything.
+ if (fromScene == toScene || swipeTransition._currentScene != toScene) {
+ return
+ }
- // If the swipe was not committed, don't do anything.
- if (fromScene == toScene || transition._currentScene != toScene) {
- return
- }
+ // If the offset is past the distance then let's change fromScene so that the user can swipe
+ // to the next screen or go back to the previous one.
+ val offset = swipeTransition.dragOffset
+ val absoluteDistance = swipeTransition.absoluteDistance
+ if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
+ swipeTransition.dragOffset += absoluteDistance
+ swipeTransition._fromScene = toScene
+ } else if (
+ offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key
+ ) {
+ swipeTransition.dragOffset -= absoluteDistance
+ swipeTransition._fromScene = toScene
+ }
- // If the offset is past the distance then let's change fromScene so that the user can swipe to
- // the next screen or go back to the previous one.
- val offset = transition.dragOffset
- val absoluteDistance = transition.absoluteDistance
- if (offset <= -absoluteDistance && fromScene.upOrLeft(orientation) == toScene.key) {
- transition.dragOffset += absoluteDistance
- transition._fromScene = toScene
- } else if (offset >= absoluteDistance && fromScene.downOrRight(orientation) == toScene.key) {
- transition.dragOffset -= absoluteDistance
- transition._fromScene = toScene
+ // Important note: toScene and distance will be updated right after this function is called,
+ // using fromScene and dragOffset.
}
- // Important note: toScene and distance will be updated right after this function is called,
- // using fromScene and dragOffset.
-}
-
-private data class TargetScene(
- val sceneKey: SceneKey,
- val distance: Float,
-)
+ private class TargetScene(
+ val sceneKey: SceneKey,
+ val distance: Float,
+ )
-private fun Scene.findTargetSceneAndDistance(
- orientation: Orientation,
- directionOffset: Float,
- layoutImpl: SceneTransitionLayoutImpl,
-): TargetScene {
- val maxDistance =
- when (orientation) {
- Orientation.Horizontal -> layoutImpl.size.width
- Orientation.Vertical -> layoutImpl.size.height
- }.toFloat()
-
- val upOrLeft = upOrLeft(orientation)
- val downOrRight = downOrRight(orientation)
-
- // Compute the target scene depending on the current offset.
- return when {
- directionOffset < 0f && upOrLeft != null -> {
- TargetScene(
- sceneKey = upOrLeft,
- distance = -maxDistance,
- )
- }
- directionOffset > 0f && downOrRight != null -> {
- TargetScene(
- sceneKey = downOrRight,
- distance = maxDistance,
- )
- }
- else -> {
- TargetScene(
- sceneKey = key,
- distance = 0f,
- )
+ private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene {
+ val maxDistance =
+ when (orientation) {
+ Orientation.Horizontal -> layoutImpl.size.width
+ Orientation.Vertical -> layoutImpl.size.height
+ }.toFloat()
+
+ val upOrLeft = upOrLeft(orientation)
+ val downOrRight = downOrRight(orientation)
+
+ // Compute the target scene depending on the current offset.
+ return when {
+ directionOffset < 0f && upOrLeft != null -> {
+ TargetScene(
+ sceneKey = upOrLeft,
+ distance = -maxDistance,
+ )
+ }
+ directionOffset > 0f && downOrRight != null -> {
+ TargetScene(
+ sceneKey = downOrRight,
+ distance = maxDistance,
+ )
+ }
+ else -> {
+ TargetScene(
+ sceneKey = key,
+ distance = 0f,
+ )
+ }
}
}
-}
-private fun CoroutineScope.onDragStopped(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- velocity: Float,
- velocityThreshold: Float,
- positionalThreshold: Float,
- canChangeScene: Boolean = true,
-) {
- // The state was changed since the drag started; don't do anything.
- if (layoutImpl.state.transitionState != transition) {
- return
- }
+ internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
+ // The state was changed since the drag started; don't do anything.
+ if (!isDrivingTransition) {
+ return
+ }
- // We were not animating.
- if (transition._fromScene == transition._toScene) {
- layoutImpl.state.transitionState = TransitionState.Idle(transition._fromScene.key)
- return
- }
+ fun animateTo(targetScene: Scene, targetOffset: Float) {
+ // If the effective current scene changed, it should be reflected right now in the
+ // current scene state, even before the settle animation is ongoing. That way all the
+ // swipeables and back handlers will be refreshed and the user can for instance quickly
+ // swipe vertically from A => B then horizontally from B => C, or swipe from A => B then
+ // immediately go back B => A.
+ if (targetScene != swipeTransition._currentScene) {
+ swipeTransition._currentScene = targetScene
+ layoutImpl.onChangeScene(targetScene.key)
+ }
- // Compute the destination scene (and therefore offset) to settle in.
- val targetScene: Scene
- val targetOffset: Float
- val offset = transition.dragOffset
- val distance = transition.distance
- if (
- canChangeScene &&
- shouldCommitSwipe(
- offset,
- distance,
- velocity,
- velocityThreshold,
- positionalThreshold,
- wasCommitted = transition._currentScene == transition._toScene,
+ animateOffset(
+ initialVelocity = velocity,
+ targetOffset = targetOffset,
+ targetScene = targetScene.key
)
- ) {
- targetOffset = distance
- targetScene = transition._toScene
- } else {
- targetOffset = 0f
- targetScene = transition._fromScene
- }
+ }
- // If the effective current scene changed, it should be reflected right now in the current scene
- // state, even before the settle animation is ongoing. That way all the swipeables and back
- // handlers will be refreshed and the user can for instance quickly swipe vertically from A => B
- // then horizontally from B => C, or swipe from A => B then immediately go back B => A.
- if (targetScene != transition._currentScene) {
- transition._currentScene = targetScene
- layoutImpl.onChangeScene(targetScene.key)
- }
+ val fromScene = swipeTransition._fromScene
+ if (canChangeScene) {
+ // If we are halfway between two scenes, we check what the target will be based on the
+ // velocity and offset of the transition, then we launch the animation.
- animateOffset(
- transition = transition,
- layoutImpl = layoutImpl,
- initialVelocity = velocity,
- targetOffset = targetOffset,
- targetScene = targetScene.key
- )
-}
+ val toScene = swipeTransition._toScene
+ if (fromScene == toScene) {
+ // We were not animating.
+ transitionState = TransitionState.Idle(fromScene.key)
+ return
+ }
-/**
- * Whether the swipe to the target scene should be committed or not. This is inspired by
- * SwipeableV2.computeTarget().
- */
-private fun shouldCommitSwipe(
- offset: Float,
- distance: Float,
- velocity: Float,
- velocityThreshold: Float,
- positionalThreshold: Float,
- wasCommitted: Boolean,
-): Boolean {
- fun isCloserToTarget(): Boolean {
- return (offset - distance).absoluteValue < offset.absoluteValue
+ // Compute the destination scene (and therefore offset) to settle in.
+ val offset = swipeTransition.dragOffset
+ val distance = swipeTransition.distance
+ if (
+ shouldCommitSwipe(
+ offset,
+ distance,
+ velocity,
+ wasCommitted = swipeTransition._currentScene == toScene,
+ )
+ ) {
+ // Animate to the next scene
+ animateTo(targetScene = toScene, targetOffset = distance)
+ } else {
+ // Animate to the initial scene
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ }
+ } else {
+ // We are doing an overscroll animation between scenes. In this case, we can also start
+ // from the idle position.
+
+ val startFromIdlePosition = swipeTransition.dragOffset == 0f
+
+ if (startFromIdlePosition) {
+ // If there is a next scene, we start the overscroll animation.
+ val target = fromScene.findTargetSceneAndDistance(velocity)
+ val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+ if (isValidTarget) {
+ swipeTransition._toScene = layoutImpl.scene(target.sceneKey)
+ swipeTransition._distance = target.distance
+
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ } else {
+ // We will not animate
+ transitionState = TransitionState.Idle(fromScene.key)
+ }
+ } else {
+ // We were between two scenes: animate to the initial scene.
+ animateTo(targetScene = fromScene, targetOffset = 0f)
+ }
+ }
}
- // Swiping up or left.
- if (distance < 0f) {
- return if (offset > 0f || velocity >= velocityThreshold) {
+ /**
+ * Whether the swipe to the target scene should be committed or not. This is inspired by
+ * SwipeableV2.computeTarget().
+ */
+ private fun shouldCommitSwipe(
+ offset: Float,
+ distance: Float,
+ velocity: Float,
+ wasCommitted: Boolean,
+ ): Boolean {
+ fun isCloserToTarget(): Boolean {
+ return (offset - distance).absoluteValue < offset.absoluteValue
+ }
+
+ // Swiping up or left.
+ if (distance < 0f) {
+ return if (offset > 0f || velocity >= velocityThreshold) {
+ false
+ } else {
+ velocity <= -velocityThreshold ||
+ (offset <= -positionalThreshold && !wasCommitted) ||
+ isCloserToTarget()
+ }
+ }
+
+ // Swiping down or right.
+ return if (offset < 0f || velocity <= -velocityThreshold) {
false
} else {
- velocity <= -velocityThreshold ||
- (offset <= -positionalThreshold && !wasCommitted) ||
+ velocity >= velocityThreshold ||
+ (offset >= positionalThreshold && !wasCommitted) ||
isCloserToTarget()
}
}
- // Swiping down or right.
- return if (offset < 0f || velocity <= -velocityThreshold) {
- false
- } else {
- velocity >= velocityThreshold ||
- (offset >= positionalThreshold && !wasCommitted) ||
- isCloserToTarget()
- }
-}
-
-private fun CoroutineScope.animateOffset(
- transition: SwipeTransition,
- layoutImpl: SceneTransitionLayoutImpl,
- initialVelocity: Float,
- targetOffset: Float,
- targetScene: SceneKey,
-) {
- transition.startOffsetAnimation {
- launch {
- if (!transition.isAnimatingOffset) {
- transition.offsetAnimatable.snapTo(transition.dragOffset)
+ private fun animateOffset(
+ initialVelocity: Float,
+ targetOffset: Float,
+ targetScene: SceneKey,
+ ) {
+ swipeTransition.startOffsetAnimation {
+ coroutineScope.launch {
+ if (!isAnimatingOffset) {
+ swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset)
}
- transition.isAnimatingOffset = true
+ isAnimatingOffset = true
- transition.offsetAnimatable.animateTo(
+ swipeTransition.offsetAnimatable.animateTo(
targetOffset,
// TODO(b/290184746): Make this spring spec configurable.
spring(
@@ -471,192 +434,233 @@ private fun CoroutineScope.animateOffset(
initialVelocity = initialVelocity,
)
+ isAnimatingOffset = false
+
// Now that the animation is done, the state should be idle. Note that if the state
// was changed since this animation started, some external code changed it and we
// shouldn't do anything here. Note also that this job will be cancelled in the case
// where the user intercepts this swipe.
- if (layoutImpl.state.transitionState == transition) {
- layoutImpl.state.transitionState = TransitionState.Idle(targetScene)
+ if (isDrivingTransition) {
+ transitionState = TransitionState.Idle(targetScene)
}
}
- .also { it.invokeOnCompletion { transition.isAnimatingOffset = false } }
+ }
}
-}
-private fun CoroutineScope.animateOverscroll(
- layoutImpl: SceneTransitionLayoutImpl,
- transition: SwipeTransition,
- velocity: Velocity,
- orientation: Orientation,
-): Velocity {
- val velocityAmount =
- when (orientation) {
- Orientation.Vertical -> velocity.y
- Orientation.Horizontal -> velocity.x
+ private class SwipeTransition(initialScene: Scene) : TransitionState.Transition {
+ var _currentScene by mutableStateOf(initialScene)
+ override val currentScene: SceneKey
+ get() = _currentScene.key
+
+ var _fromScene by mutableStateOf(initialScene)
+ override val fromScene: SceneKey
+ get() = _fromScene.key
+
+ var _toScene by mutableStateOf(initialScene)
+ override val toScene: SceneKey
+ get() = _toScene.key
+
+ override val progress: Float
+ get() {
+ val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
+ if (distance == 0f) {
+ // This can happen only if fromScene == toScene.
+ error(
+ "Transition.progress should be called only when Transition.fromScene != " +
+ "Transition.toScene"
+ )
+ }
+ return offset / distance
+ }
+
+ override val isUserInputDriven = true
+
+ /** The current offset caused by the drag gesture. */
+ var dragOffset by mutableFloatStateOf(0f)
+
+ /**
+ * Whether the offset is animated (the user lifted their finger) or if it is driven by
+ * gesture.
+ */
+ var isAnimatingOffset by mutableStateOf(false)
+
+ /** The animatable used to animate the offset once the user lifted its finger. */
+ val offsetAnimatable = Animatable(0f, OffsetVisibilityThreshold)
+
+ /** Job to check that there is at most one offset animation in progress. */
+ private var offsetAnimationJob: Job? = null
+
+ /** Ends any previous [offsetAnimationJob] and runs the new [job]. */
+ fun startOffsetAnimation(job: () -> Job) {
+ stopOffsetAnimation()
+ offsetAnimationJob = job()
+ }
+
+ /** Stops any ongoing offset animation. */
+ fun stopOffsetAnimation() {
+ offsetAnimationJob?.cancel()
+
+ if (isAnimatingOffset) {
+ isAnimatingOffset = false
+ dragOffset = offsetAnimatable.value
+ }
}
- if (velocityAmount == 0f) {
- // There is no remaining velocity
- return Velocity.Zero
+ /** The absolute distance between [fromScene] and [toScene]. */
+ var absoluteDistance = 0f
+
+ /**
+ * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is
+ * above or to the left of [toScene].
+ */
+ var _distance by mutableFloatStateOf(0f)
+ val distance: Float
+ get() = _distance
}
- val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
- val target = fromScene.findTargetSceneAndDistance(orientation, velocityAmount, layoutImpl)
- val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key
+ companion object {
+ private const val TAG = "SceneGestureHandler"
+ }
+}
- if (!isValidTarget || layoutImpl.state.transitionState == transition) {
- // We have not found a valid target or we are already in a transition
- return Velocity.Zero
+private class SceneDraggableHandler(
+ private val gestureHandler: SceneGestureHandler,
+) : DraggableHandler {
+ override suspend fun onDragStarted(coroutineScope: CoroutineScope, startedPosition: Offset) {
+ gestureHandler.gestureWithPriority = this
+ gestureHandler.onDragStarted()
}
- transition._currentScene = fromScene
- transition._fromScene = fromScene
- transition._toScene = layoutImpl.scene(target.sceneKey)
- transition._distance = target.distance
- transition.absoluteDistance = target.distance.absoluteValue
- transition.stopOffsetAnimation()
- transition.dragOffset = 0f
-
- layoutImpl.state.transitionState = transition
-
- animateOffset(
- transition = transition,
- layoutImpl = layoutImpl,
- initialVelocity = velocityAmount,
- targetOffset = 0f,
- targetScene = fromScene.key
- )
+ override fun onDelta(pixels: Float) {
+ if (gestureHandler.gestureWithPriority == this) {
+ gestureHandler.onDrag(delta = pixels)
+ }
+ }
- // The animateOffset animation consumes any remaining velocity.
- return velocity
+ override suspend fun onDragStopped(coroutineScope: CoroutineScope, velocity: Float) {
+ if (gestureHandler.gestureWithPriority == this) {
+ gestureHandler.gestureWithPriority = null
+ gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
+ }
+ }
}
-/**
- * The number of pixels below which there won't be a visible difference in the transition and from
- * which the animation can stop.
- */
-private const val OffsetVisibilityThreshold = 0.5f
+@VisibleForTesting
+class SceneNestedScrollHandler(
+ private val gestureHandler: SceneGestureHandler,
+) : NestedScrollHandler {
+ override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
-@Composable
-private fun rememberSwipeToSceneNestedScrollConnection(
- orientation: Orientation,
- coroutineScope: CoroutineScope,
- draggableState: DraggableState,
- transition: SwipeTransition,
- layoutImpl: SceneTransitionLayoutImpl,
- velocityThreshold: Float,
- positionalThreshold: Float,
-): PriorityPostNestedScrollConnection {
- val density = LocalDensity.current
- val scrollConnection =
- remember(
- orientation,
- coroutineScope,
- draggableState,
- transition,
- layoutImpl,
- velocityThreshold,
- positionalThreshold,
- density,
- ) {
- fun Offset.toAmount() =
- when (orientation) {
- Orientation.Horizontal -> x
- Orientation.Vertical -> y
- }
+ private fun Offset.toAmount() =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
+ }
- fun Velocity.toAmount() =
- when (orientation) {
- Orientation.Horizontal -> x
- Orientation.Vertical -> y
- }
+ private fun Velocity.toAmount() =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> x
+ Orientation.Vertical -> y
+ }
- fun Float.toOffset() =
- when (orientation) {
- Orientation.Horizontal -> Offset(x = this, y = 0f)
- Orientation.Vertical -> Offset(x = 0f, y = this)
- }
+ private fun Float.toOffset() =
+ when (gestureHandler.orientation) {
+ Orientation.Horizontal -> Offset(x = this, y = 0f)
+ Orientation.Vertical -> Offset(x = 0f, y = this)
+ }
- // The next potential scene is calculated during the canStart
- var nextScene: SceneKey? = null
-
- // This is the scene on which we will have priority during the scroll gesture.
- var priorityScene: SceneKey? = null
-
- // If we performed a long gesture before entering priority mode, we would have to avoid
- // moving on to the next scene.
- var gestureStartedOnNestedChild = false
-
- PriorityPostNestedScrollConnection(
- canStart = { offsetAvailable, offsetBeforeStart ->
- val amount = offsetAvailable.toAmount()
- if (amount == 0f) return@PriorityPostNestedScrollConnection false
-
- gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
-
- val fromScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
- nextScene =
- when {
- amount < 0f -> fromScene.upOrLeft(orientation)
- amount > 0f -> fromScene.downOrRight(orientation)
- else -> null
- }
-
- nextScene != null
- },
- canContinueScroll = { priorityScene == transition._toScene.key },
- onStart = {
- priorityScene = nextScene
- onDragStarted(layoutImpl, transition, orientation)
- },
- onScroll = { offsetAvailable ->
- val amount = offsetAvailable.toAmount()
-
- // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture
- // is initiated in a nested child.
-
- // Appends a new coroutine to attempt to drag by [amount] px. In this case we
- // are assuming that the [coroutineScope] is tied to the main thread and that
- // calls to [launch] are therefore queued.
- coroutineScope.launch { draggableState.drag { dragBy(amount) } }
-
- amount.toOffset()
- },
- onStop = { velocityAvailable ->
- priorityScene = null
-
- coroutineScope.onDragStopped(
- layoutImpl = layoutImpl,
- transition = transition,
- velocity = velocityAvailable.toAmount(),
- velocityThreshold = velocityThreshold,
- positionalThreshold = positionalThreshold,
- canChangeScene = !gestureStartedOnNestedChild
- )
+ private fun nestedScrollConnection(): PriorityNestedScrollConnection {
+ // The next potential scene is calculated during the canStart
+ var nextScene: SceneKey? = null
- // The onDragStopped animation consumes any remaining velocity.
- velocityAvailable
- },
- onPostFling = { velocityAvailable ->
- // If there is any velocity left, we can try running an overscroll animation
- // between scenes.
- coroutineScope.animateOverscroll(
- layoutImpl = layoutImpl,
- transition = transition,
- velocity = velocityAvailable,
- orientation = orientation
- )
- },
- )
- }
- DisposableEffect(scrollConnection) {
- onDispose {
- coroutineScope.launch {
- // This should ensure that the draggableState is in a consistent state and that it
- // does not cause any unexpected behavior.
- scrollConnection.reset()
+ // This is the scene on which we will have priority during the scroll gesture.
+ var priorityScene: SceneKey? = null
+
+ // If we performed a long gesture before entering priority mode, we would have to avoid
+ // moving on to the next scene.
+ var gestureStartedOnNestedChild = false
+
+ fun findNextScene(amount: Float): SceneKey? {
+ val fromScene = gestureHandler.currentScene
+ return when {
+ amount < 0f -> fromScene.upOrLeft(gestureHandler.orientation)
+ amount > 0f -> fromScene.downOrRight(gestureHandler.orientation)
+ else -> null
}
}
+
+ return PriorityNestedScrollConnection(
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
+ gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+
+ val canInterceptPreScroll =
+ gestureHandler.isDrivingTransition &&
+ !gestureStartedOnNestedChild &&
+ offsetAvailable.toAmount() != 0f
+
+ if (!canInterceptPreScroll) return@PriorityNestedScrollConnection false
+
+ nextScene = gestureHandler.swipeTransitionToScene.key
+
+ true
+ },
+ canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
+ val amount = offsetAvailable.toAmount()
+ if (amount == 0f) return@PriorityNestedScrollConnection false
+
+ gestureStartedOnNestedChild = offsetBeforeStart != Offset.Zero
+ nextScene = findNextScene(amount)
+ nextScene != null
+ },
+ canStartPostFling = { velocityAvailable ->
+ val amount = velocityAvailable.toAmount()
+ if (amount == 0f) return@PriorityNestedScrollConnection false
+
+ // We could start an overscroll animation
+ gestureStartedOnNestedChild = true
+ nextScene = findNextScene(amount)
+ nextScene != null
+ },
+ canContinueScroll = { priorityScene == gestureHandler.swipeTransitionToScene.key },
+ onStart = {
+ gestureHandler.gestureWithPriority = this
+ priorityScene = nextScene
+ gestureHandler.onDragStarted()
+ },
+ onScroll = { offsetAvailable ->
+ if (gestureHandler.gestureWithPriority != this) {
+ return@PriorityNestedScrollConnection Offset.Zero
+ }
+
+ val amount = offsetAvailable.toAmount()
+
+ // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
+ // initiated in a nested child.
+ gestureHandler.onDrag(amount)
+
+ amount.toOffset()
+ },
+ onStop = { velocityAvailable ->
+ if (gestureHandler.gestureWithPriority != this) {
+ return@PriorityNestedScrollConnection Velocity.Zero
+ }
+
+ priorityScene = null
+
+ gestureHandler.onDragStopped(
+ velocity = velocityAvailable.toAmount(),
+ canChangeScene = !gestureStartedOnNestedChild
+ )
+
+ // The onDragStopped animation consumes any remaining velocity.
+ velocityAvailable
+ },
+ )
}
- return scrollConnection
}
+
+/**
+ * The number of pixels below which there won't be a visible difference in the transition and from
+ * which the animation can stop.
+ */
+private const val OffsetVisibilityThreshold = 0.5f
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 793a9a59405a..824c10b88a9b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -22,22 +22,23 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
/**
- * This [NestedScrollConnection] waits for a child to scroll ([onPostScroll]), and then decides (via
- * [canStart]) if it should take over scrolling. If it does, it will scroll before its children,
- * until [canContinueScroll] allows it.
+ * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
+ * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling.
+ * If it does, it will scroll before its children, until [canContinueScroll] allows it.
*
* Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
* after [onStart].
*
* @sample com.android.compose.animation.scene.rememberSwipeToSceneNestedScrollConnection
*/
-class PriorityPostNestedScrollConnection(
- private val canStart: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+class PriorityNestedScrollConnection(
+ private val canStartPreScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+ private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean,
+ private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean,
private val canContinueScroll: () -> Boolean,
private val onStart: () -> Unit,
private val onScroll: (offsetAvailable: Offset) -> Offset,
private val onStop: (velocityAvailable: Velocity) -> Velocity,
- private val onPostFling: suspend (velocityAvailable: Velocity) -> Velocity,
) : NestedScrollConnection {
/** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
@@ -57,26 +58,21 @@ class PriorityPostNestedScrollConnection(
if (
isPriorityMode ||
source == NestedScrollSource.Fling ||
- !canStart(available, offsetBeforeStart)
+ !canStartPostScroll(available, offsetBeforeStart)
) {
// The priority mode cannot start so we won't consume the available offset.
return Offset.Zero
}
- // Step 1: It's our turn! We start capturing scroll events when one of our children has an
- // available offset following a scroll event.
- isPriorityMode = true
-
- // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
- // lifted (step 3b), or this object has been destroyed (step 3c).
- onStart()
-
- return onScroll(available)
+ return onPriorityStart(available)
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (!isPriorityMode) {
if (source != NestedScrollSource.Fling) {
+ if (canStartPreScroll(available, offsetScrolledBeforePriorityMode)) {
+ return onPriorityStart(available)
+ }
// We want to track the amount of offset consumed before entering priority mode
offsetScrolledBeforePriorityMode += available
}
@@ -87,6 +83,11 @@ class PriorityPostNestedScrollConnection(
if (!canContinueScroll()) {
// Step 3a: We have lost priority and we no longer need to intercept scroll events.
onPriorityStop(velocity = Velocity.Zero)
+
+ // We've just reset offsetScrolledBeforePriorityMode to Offset.Zero
+ // We want to track the amount of offset consumed before entering priority mode
+ offsetScrolledBeforePriorityMode += available
+
return Offset.Zero
}
@@ -101,7 +102,14 @@ class PriorityPostNestedScrollConnection(
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- return onPostFling(available)
+ if (!canStartPostFling(available)) {
+ return Velocity.Zero
+ }
+
+ onPriorityStart(available = Offset.Zero)
+
+ // This is the last event of a scroll gesture.
+ return onPriorityStop(available)
}
/** Method to call before destroying the object or to reset the initial state. */
@@ -110,8 +118,23 @@ class PriorityPostNestedScrollConnection(
onPriorityStop(velocity = Velocity.Zero)
}
- private fun onPriorityStop(velocity: Velocity): Velocity {
+ private fun onPriorityStart(available: Offset): Offset {
+ if (isPriorityMode) {
+ error("This should never happen, onPriorityStart() was called when isPriorityMode")
+ }
+
+ // Step 1: It's our turn! We start capturing scroll events when one of our children has an
+ // available offset following a scroll event.
+ isPriorityMode = true
+
+ // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
+ // lifted (step 3b), or this object has been destroyed (step 3c).
+ onStart()
+ return onScroll(available)
+ }
+
+ private fun onPriorityStop(velocity: Velocity): Velocity {
// We can restart tracking the consumed offsets from scratch.
offsetScrolledBeforePriorityMode = Offset.Zero
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
new file mode 100644
index 000000000000..6791a85ff21c
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -0,0 +1,363 @@
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.material3.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.Velocity
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.android.compose.animation.scene.TestScenes.SceneB
+import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TransitionState.Idle
+import com.android.compose.animation.scene.TransitionState.Transition
+import com.android.compose.test.MonotonicClockTestScope
+import com.android.compose.test.runMonotonicClockTest
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val SCREEN_SIZE = 100f
+
+@RunWith(AndroidJUnit4::class)
+class SceneGestureHandlerTest {
+ private class TestGestureScope(
+ val coroutineScope: MonotonicClockTestScope,
+ ) {
+ private var internalCurrentScene: SceneKey by mutableStateOf(SceneA)
+
+ private val layoutState: SceneTransitionLayoutState =
+ SceneTransitionLayoutState(internalCurrentScene)
+
+ private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
+ scene(
+ key = SceneA,
+ userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC),
+ ) {
+ Text("SceneA")
+ }
+ scene(SceneB) { Text("SceneB") }
+ scene(SceneC) { Text("SceneC") }
+ }
+
+ val sceneGestureHandler =
+ SceneGestureHandler(
+ layoutImpl =
+ SceneTransitionLayoutImpl(
+ onChangeScene = { internalCurrentScene = it },
+ builder = scenesBuilder,
+ transitions = EmptyTestTransitions,
+ state = layoutState,
+ density = Density(1f)
+ )
+ .also { it.size = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) },
+ orientation = Orientation.Vertical,
+ coroutineScope = coroutineScope,
+ )
+
+ val draggable = sceneGestureHandler.draggable
+
+ val nestedScroll = sceneGestureHandler.nestedScroll.connection
+
+ val velocityThreshold = sceneGestureHandler.velocityThreshold
+
+ // 10% of the screen
+ val deltaInPixels10 = SCREEN_SIZE * 0.1f
+
+ // Offset y: 10% of the screen
+ val offsetY10 = Offset(x = 0f, y = deltaInPixels10)
+
+ val transitionState: TransitionState
+ get() = layoutState.transitionState
+
+ fun advanceUntilIdle() {
+ coroutineScope.testScheduler.advanceUntilIdle()
+ }
+
+ fun runCurrent() {
+ coroutineScope.testScheduler.runCurrent()
+ }
+
+ fun assertScene(currentScene: SceneKey, isIdle: Boolean) {
+ val idleMsg = if (isIdle) "MUST" else "MUST NOT"
+ assertWithMessage("transitionState $idleMsg be Idle")
+ .that(transitionState is Idle)
+ .isEqualTo(isIdle)
+ assertThat(transitionState.currentScene).isEqualTo(currentScene)
+ }
+ }
+
+ @OptIn(ExperimentalTestApi::class)
+ private fun runGestureTest(block: suspend TestGestureScope.() -> Unit) {
+ runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
+ }
+
+ @Test
+ fun testPreconditions() = runGestureTest { assertScene(currentScene = SceneA, isIdle = true) }
+
+ @Test
+ fun onDragStarted_shouldStartATransition() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+ }
+
+ @Test
+ fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+ val transition = transitionState as Transition
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertThat(transition.progress).isEqualTo(0.1f)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertThat(transition.progress).isEqualTo(0.2f)
+ }
+
+ @Test
+ fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(
+ coroutineScope = coroutineScope,
+ velocity = velocityThreshold - 0.01f,
+ )
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(
+ coroutineScope = coroutineScope,
+ velocity = velocityThreshold,
+ )
+ assertScene(currentScene = SceneC, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneC, isIdle = true)
+ }
+
+ @Test
+ fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(coroutineScope = coroutineScope, velocity = 0f)
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDelta(pixels = deltaInPixels10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ draggable.onDragStopped(
+ coroutineScope = coroutineScope,
+ velocity = velocityThreshold,
+ )
+
+ // The stop animation is not started yet
+ assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+
+ runCurrent()
+
+ assertThat(sceneGestureHandler.isAnimatingOffset).isTrue()
+ assertThat(sceneGestureHandler.isDrivingTransition).isTrue()
+ assertScene(currentScene = SceneC, isIdle = false)
+
+ // Start a new gesture while the offset is animating
+ draggable.onDragStarted(coroutineScope = coroutineScope, startedPosition = Offset.Zero)
+ assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
+ }
+
+ @Test
+ fun onInitialPreScroll_doNotChangeState() = runGestureTest {
+ nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun onPostScrollWithNothingAvailable_doNotChangeState() = runGestureTest {
+ val consumed =
+ nestedScroll.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+
+ assertScene(currentScene = SceneA, isIdle = true)
+ assertThat(consumed).isEqualTo(Offset.Zero)
+ }
+
+ @Test
+ fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest {
+ val consumed =
+ nestedScroll.onPostScroll(
+ consumed = Offset.Zero,
+ available = offsetY10,
+ source = NestedScrollSource.Drag
+ )
+
+ assertScene(currentScene = SceneA, isIdle = false)
+ val transition = transitionState as Transition
+ assertThat(transition.progress).isEqualTo(0.1f)
+ assertThat(consumed).isEqualTo(offsetY10)
+ }
+
+ private fun TestGestureScope.nestedScrollEvents(
+ available: Offset,
+ consumedByScroll: Offset = Offset.Zero,
+ ) {
+ val consumedByPreScroll =
+ nestedScroll.onPreScroll(available = available, source = NestedScrollSource.Drag)
+ val consumed = consumedByPreScroll + consumedByScroll
+ nestedScroll.onPostScroll(
+ consumed = consumed,
+ available = available - consumed,
+ source = NestedScrollSource.Drag
+ )
+ }
+
+ @Test
+ fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest {
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ val transition = transitionState as Transition
+ assertThat(transition.progress).isEqualTo(0.1f)
+
+ // start intercept preScroll
+ val consumed =
+ nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag)
+ assertThat(transition.progress).isEqualTo(0.2f)
+
+ // do nothing on postScroll
+ nestedScroll.onPostScroll(
+ consumed = consumed,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(transition.progress).isEqualTo(0.2f)
+
+ nestedScrollEvents(available = offsetY10)
+ assertThat(transition.progress).isEqualTo(0.3f)
+ assertScene(currentScene = SceneA, isIdle = false)
+ }
+
+ @Test
+ fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ nestedScroll.onPreFling(available = Velocity.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun onPreFling_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ assertScene(currentScene = SceneC, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneC, isIdle = true)
+ }
+
+ @Test
+ fun scrollStartedInScene_doOverscrollAnimation() = runGestureTest {
+ // we started the scroll in the scene
+ nestedScrollEvents(available = offsetY10, consumedByScroll = offsetY10)
+
+ // now we can intercept the scroll events
+ nestedScrollEvents(available = offsetY10)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ // should start an overscroll animation (the gesture started in the scene)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
+ draggable.onDelta(deltaInPixels10)
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+ @Test
+ fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
+ draggable.onDragStopped(coroutineScope, velocityThreshold)
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
+ nestedScroll.onPreFling(Velocity(0f, velocityThreshold))
+ assertScene(currentScene = SceneA, isIdle = true)
+ }
+
+ @Test
+ fun startNestedScrollWhileDragging() = runGestureTest {
+ draggable.onDragStarted(coroutineScope, Offset.Zero)
+ assertScene(currentScene = SceneA, isIdle = false)
+ val transition = transitionState as Transition
+
+ draggable.onDelta(deltaInPixels10)
+ assertThat(transition.progress).isEqualTo(0.1f)
+
+ // now we can intercept the scroll events
+ nestedScrollEvents(available = offsetY10)
+ assertThat(transition.progress).isEqualTo(0.2f)
+
+ // this should be ignored, we are scrolling now!
+ draggable.onDragStopped(coroutineScope, velocityThreshold)
+ assertScene(currentScene = SceneA, isIdle = false)
+
+ nestedScrollEvents(available = offsetY10)
+ assertThat(transition.progress).isEqualTo(0.3f)
+
+ nestedScrollEvents(available = offsetY10)
+ assertThat(transition.progress).isEqualTo(0.4f)
+
+ nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold))
+ assertScene(currentScene = SceneC, isIdle = false)
+
+ // wait for the stop animation
+ advanceUntilIdle()
+ assertScene(currentScene = SceneC, isIdle = true)
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 8e2b77a2f2a0..122774bb28fe 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityPostNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -29,20 +29,22 @@ import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class PriorityPostNestedScrollConnectionTest {
- private var canStart = false
+class PriorityNestedScrollConnectionTest {
+ private var canStartPreScroll = false
+ private var canStartPostScroll = false
+ private var canStartPostFling = false
private var canContinueScroll = false
private var isStarted = false
private var lastScroll: Offset? = null
private var returnOnScroll = Offset.Zero
private var lastStop: Velocity? = null
private var returnOnStop = Velocity.Zero
- private var lastOnPostFling: Velocity? = null
- private var returnOnPostFling = Velocity.Zero
private val scrollConnection =
- PriorityPostNestedScrollConnection(
- canStart = { _, _ -> canStart },
+ PriorityNestedScrollConnection(
+ canStartPreScroll = { _, _ -> canStartPreScroll },
+ canStartPostScroll = { _, _ -> canStartPostScroll },
+ canStartPostFling = { canStartPostFling },
canContinueScroll = { canContinueScroll },
onStart = { isStarted = true },
onScroll = {
@@ -53,10 +55,6 @@ class PriorityPostNestedScrollConnectionTest {
lastStop = it
returnOnStop
},
- onPostFling = {
- lastOnPostFling = it
- returnOnPostFling
- },
)
private val offset1 = Offset(1f, 1f)
@@ -64,8 +62,29 @@ class PriorityPostNestedScrollConnectionTest {
private val velocity1 = Velocity(1f, 1f)
private val velocity2 = Velocity(2f, 2f)
- private fun startPriorityMode() {
- canStart = true
+ @Test
+ fun step1_priorityModeShouldStartOnlyOnPreScroll() = runTest {
+ canStartPreScroll = true
+
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ assertThat(isStarted).isEqualTo(true)
+ }
+
+ private fun startPriorityModePostScroll() {
+ canStartPostScroll = true
scrollConnection.onPostScroll(
consumed = Offset.Zero,
available = Offset.Zero,
@@ -75,7 +94,7 @@ class PriorityPostNestedScrollConnectionTest {
@Test
fun step1_priorityModeShouldStartOnlyOnPostScroll() = runTest {
- canStart = true
+ canStartPostScroll = true
scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
assertThat(isStarted).isEqualTo(false)
@@ -86,7 +105,7 @@ class PriorityPostNestedScrollConnectionTest {
scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
assertThat(isStarted).isEqualTo(false)
- startPriorityMode()
+ startPriorityModePostScroll()
assertThat(isStarted).isEqualTo(true)
}
@@ -99,13 +118,13 @@ class PriorityPostNestedScrollConnectionTest {
)
assertThat(isStarted).isEqualTo(false)
- startPriorityMode()
+ startPriorityModePostScroll()
assertThat(isStarted).isEqualTo(true)
}
@Test
fun step1_onPriorityModeStarted_receiveAvailableOffset() {
- canStart = true
+ canStartPostScroll = true
scrollConnection.onPostScroll(
consumed = offset1,
@@ -118,7 +137,7 @@ class PriorityPostNestedScrollConnectionTest {
@Test
fun step2_onPriorityMode_shouldContinueIfAllowed() {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = true
scrollConnection.onPreScroll(available = offset1, source = NestedScrollSource.Drag)
@@ -132,7 +151,7 @@ class PriorityPostNestedScrollConnectionTest {
@Test
fun step3a_onPriorityMode_shouldStopIfCannotContinue() {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = false
scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
@@ -142,7 +161,7 @@ class PriorityPostNestedScrollConnectionTest {
@Test
fun step3b_onPriorityMode_shouldStopOnFling() = runTest {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = true
scrollConnection.onPreFling(available = Velocity.Zero)
@@ -152,7 +171,7 @@ class PriorityPostNestedScrollConnectionTest {
@Test
fun step3c_onPriorityMode_shouldStopOnReset() {
- startPriorityMode()
+ startPriorityModePostScroll()
canContinueScroll = true
scrollConnection.reset()
@@ -162,11 +181,34 @@ class PriorityPostNestedScrollConnectionTest {
@Test
fun receive_onPostFling() = runTest {
+ canStartPostFling = true
+
scrollConnection.onPostFling(
consumed = velocity1,
available = velocity2,
)
- assertThat(lastOnPostFling).isEqualTo(velocity2)
+ assertThat(lastStop).isEqualTo(velocity2)
+ }
+
+ @Test
+ fun step1_priorityModeShouldStartOnlyOnPostFling() = runTest {
+ canStartPostFling = true
+
+ scrollConnection.onPreScroll(available = Offset.Zero, source = NestedScrollSource.Drag)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostScroll(
+ consumed = Offset.Zero,
+ available = Offset.Zero,
+ source = NestedScrollSource.Drag
+ )
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPreFling(available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(false)
+
+ scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
+ assertThat(isStarted).isEqualTo(true)
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
new file mode 100644
index 000000000000..cb122dc8e25e
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/RunMonotonicClockTest.kt
@@ -0,0 +1,27 @@
+package com.android.compose.test
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.TestMonotonicFrameClock
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
+
+/**
+ * This method creates a [CoroutineScope] that can be used in animations created in a composable
+ * function.
+ *
+ * The [TestCoroutineScheduler] is passed to provide the functionality to wait for idle.
+ */
+@ExperimentalTestApi
+fun runMonotonicClockTest(block: suspend MonotonicClockTestScope.() -> Unit) = runTest {
+ // We need a CoroutineScope (like a TestScope) to create a TestMonotonicFrameClock.
+ withContext(TestMonotonicFrameClock(this)) {
+ MonotonicClockTestScope(coroutineScope = this, testScheduler = testScheduler).block()
+ }
+}
+
+class MonotonicClockTestScope(
+ coroutineScope: CoroutineScope,
+ val testScheduler: TestCoroutineScheduler
+) : CoroutineScope by coroutineScope
diff --git a/packages/SystemUI/flag_check.py b/packages/SystemUI/flag_check.py
new file mode 100755
index 000000000000..5db27d8a9529
--- /dev/null
+++ b/packages/SystemUI/flag_check.py
@@ -0,0 +1,134 @@
+#! /usr/bin/env python3
+
+import sys
+import re
+import argparse
+
+# partially copied from tools/repohooks/rh/hooks.py
+
+TEST_MSG = """Commit message is missing a "Flag:" line. It must match one of the
+following case-sensitive regex:
+
+ %s
+
+The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags.
+
+As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the
+flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD).
+
+Some examples below:
+
+Flag: NONE
+Flag: NA
+Flag: LEGACY ENABLE_ONE_SEARCH DISABLED
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT
+Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD
+
+Check the git history for more examples. It's a regex matched field.
+"""
+
+def main():
+ """Check the commit message for a 'Flag:' line."""
+ parser = argparse.ArgumentParser(
+ description='Check the commit message for a Flag: line.')
+ parser.add_argument('--msg',
+ metavar='msg',
+ type=str,
+ nargs='?',
+ default='HEAD',
+ help='commit message to process.')
+ parser.add_argument(
+ '--files',
+ metavar='files',
+ nargs='?',
+ default='',
+ help=
+ 'PREUPLOAD_FILES in repo upload to determine whether the check should run for the files.')
+ parser.add_argument(
+ '--project',
+ metavar='project',
+ type=str,
+ nargs='?',
+ default='',
+ help=
+ 'REPO_PATH in repo upload to determine whether the check should run for this project.')
+
+ # Parse the arguments
+ args = parser.parse_args()
+ desc = args.msg
+ files = args.files
+ project = args.project
+
+ if not should_run_path(project, files):
+ return
+
+ field = 'Flag'
+ none = '(NONE|NA|N\/A)' # NONE|NA|N/A
+
+ typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG]
+
+ # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH
+ # Aconfig Flag name format = "packageName"."flagName"
+ # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3
+ # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check.
+ #common_typos_disable
+ flagName = '([a-zA-z0-9_.])+'
+
+ #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)]
+ stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)'
+ #common_typos_enable
+
+ readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|TRUNKFOOD|NEXTFOOD'
+
+ flagRegex = fr'^{field}: .*$'
+ check_flag = re.compile(flagRegex) #Flag:
+
+ # Ignore case for flag name format.
+ flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*'
+ check_flagName = re.compile(flagNameRegex) #Flag: <flag name format>
+
+ flagError = False
+ foundFlag = []
+ # Check for multiple "Flag:" lines and all lines should match this format
+ for line in desc.splitlines():
+ if check_flag.match(line):
+ if not check_flagName.match(line):
+ flagError = True
+ break
+ foundFlag.append(line)
+
+ # Throw error if
+ # 1. No "Flag:" line is found
+ # 2. "Flag:" doesn't follow right format.
+ if (not foundFlag) or (flagError):
+ error = TEST_MSG % (readableRegexMsg)
+ print(error)
+ sys.exit(1)
+
+ sys.exit(0)
+
+
+def should_run_path(path, files):
+ """Returns a boolean if this check should run with these paths.
+ If you want to check for a particular subdirectory under the path,
+ add a check here, call should_run_files and check for a specific sub dir path in should_run_files.
+ """
+ if not path:
+ return False
+ if path == 'frameworks/base':
+ return should_run_files(files)
+ # Default case, run for all other paths which calls this script.
+ return True
+
+
+def should_run_files(files):
+ """Returns a boolean if this check should run with these files."""
+ if not files:
+ return False
+ if 'packages/SystemUI' in files:
+ return True
+ return False
+
+
+if __name__ == '__main__':
+ main()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index e20d3afaca53..e20d3afaca53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
diff --git a/packages/SystemUI/multivalentTestsForDevice b/packages/SystemUI/multivalentTestsForDevice
new file mode 120000
index 000000000000..20ee34ada103
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDevice
@@ -0,0 +1 @@
+multivalentTests \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTestsForDeviceless b/packages/SystemUI/multivalentTestsForDeviceless
new file mode 120000
index 000000000000..20ee34ada103
--- /dev/null
+++ b/packages/SystemUI/multivalentTestsForDeviceless
@@ -0,0 +1 @@
+multivalentTests \ No newline at end of file
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 9cc87fde122f..f0e3c99b007d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -57,6 +57,16 @@ public interface ActivityStarter {
@Nullable ActivityLaunchAnimator.Controller animationController);
/**
+ * Similar to {@link #startPendingIntentDismissingKeyguard}, except that it supports launching
+ * activities on top of the keyguard. If the activity supports {@code showOverLockscreen}, it
+ * will show over keyguard without first dimissing it. If it doesn't support it, calling this
+ * method is exactly the same as calling {@link #startPendingIntentDismissingKeyguard}.
+ */
+ void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent,
+ @Nullable Runnable intentSentUiThreadCallback,
+ @Nullable ActivityLaunchAnimator.Controller animationController);
+
+ /**
* The intent flag can be specified in startActivity().
*/
void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags);
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
index a9d2ee3cdfe6..403c7c500426 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/DarkIconDispatcher.java
@@ -19,7 +19,6 @@ package com.android.systemui.plugins;
import android.graphics.Color;
import android.graphics.Rect;
import android.view.View;
-import android.widget.ImageView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.plugins.annotations.DependsOn;
@@ -51,21 +50,11 @@ public interface DarkIconDispatcher {
void addDarkReceiver(DarkReceiver receiver);
/**
- * Adds a receiver to receive callbacks onDarkChanged
- */
- void addDarkReceiver(ImageView imageView);
-
- /**
* Must have been previously been added through one of the addDarkReceive methods above.
*/
void removeDarkReceiver(DarkReceiver object);
/**
- * Must have been previously been added through one of the addDarkReceive methods above.
- */
- void removeDarkReceiver(ImageView object);
-
- /**
* Used to reapply darkness on an object, must have previously been added through
* addDarkReceiver.
*/
@@ -104,8 +93,8 @@ public interface DarkIconDispatcher {
}
/**
- * @return true if more than half of the view area are in any of the given
- * areas, false otherwise
+ * @return true if more than half of the view's area is in any of the given area Rects, false
+ * otherwise
*/
static boolean isInAreas(Collection<Rect> areas, View view) {
if (areas.isEmpty()) {
@@ -120,9 +109,40 @@ public interface DarkIconDispatcher {
}
/**
- * @return true if more than half of the view area are in area, false
+ * @return true if more than half of the viewBounds are in any of the given area Rects, false
* otherwise
*/
+ static boolean isInAreas(Collection<Rect> areas, Rect viewBounds) {
+ if (areas.isEmpty()) {
+ return true;
+ }
+ for (Rect area : areas) {
+ if (isInArea(area, viewBounds)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @return true if more than half of the viewBounds are in the area Rect, false otherwise */
+ static boolean isInArea(Rect area, Rect viewBounds) {
+ if (area.isEmpty()) {
+ return true;
+ }
+ sTmpRect.set(area);
+ int left = viewBounds.left;
+ int width = viewBounds.width();
+
+ int intersectStart = Math.max(left, area.left);
+ int intersectEnd = Math.min(left + width, area.right);
+ int intersectAmount = Math.max(0, intersectEnd - intersectStart);
+
+ boolean coversFullStatusBar = area.top <= 0;
+ boolean majorityOfWidth = 2 * intersectAmount > width;
+ return majorityOfWidth && coversFullStatusBar;
+ }
+
+ /** @return true if more than half of the view's area is in the area Rect, false otherwise */
static boolean isInArea(Rect area, View view) {
if (area.isEmpty()) {
return true;
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 8daa280ee39a..087ab3a79358 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -126,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 8daa280ee39a..087ab3a79358 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -126,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 8daa280ee39a..087ab3a79358 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -126,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Draw pattern to install update later"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Device updated Enter PIN to continue."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Device updated Enter password to continue."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated Draw pattern to continue."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Device updated. Draw pattern to continue."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index 85ce9fbe0ff7..4837de2139ac 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -126,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Debuxa o padrón para instalar a actualización máis tarde"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Actualizouse o dispositivo. Mete o PIN para continuar."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Actualizouse o dispositivo. Mete o contrasinal para continuar."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón para continuar."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Actualizouse o dispositivo. Debuxa o padrón."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-iw/strings.xml b/packages/SystemUI/res-keyguard/values-iw/strings.xml
index 67601253a79d..16316cebcc7c 100644
--- a/packages/SystemUI/res-keyguard/values-iw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-iw/strings.xml
@@ -126,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"צריך למתוח את קו ביטול הנעילה כדי להתקין את העדכון מאוחר יותר"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"המכשיר עודכן. צריך להזין את קוד האימות כדי להמשיך."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"המכשיר עודכן. צריך להזין את הסיסמה כדי להמשיך."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. צריך למתוח את קו ביטול הנעילה כדי להמשיך."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"המכשיר עודכן. יש למתוח קו ביטול נעילה כדי להמשיך"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index a4a269025ba3..7378cc78e5e6 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -125,6 +125,6 @@
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"나중에 업데이트를 설치하려면 비밀번호를 입력하세요."</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"나중에 업데이트를 설치하려면 패턴을 그리세요."</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"기기가 업데이트되었습니다. 계속하려면 PIN을 입력하세요."</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속 진행하려면 비밀번호를 입력하세요."</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"기기가 업데이트되었습니다. 계속하려면 비밀번호를 입력하세요."</string>
<string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"기기가 업데이트되었습니다. 계속하려면 패턴을 그리세요."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 38b6ad9b9f2e..afbce26a945f 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -124,7 +124,7 @@
<string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပင်နံပါတ်ထည့်ပါ"</string>
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် စကားဝှက်ထည့်ပါ"</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"နောက်ပိုင်းတွင် အပ်ဒိတ်ထည့်သွင်းရန် ပုံဖော်ရေးဆွဲပါ"</string>
- <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"စက်ပစ္စည်း အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
+ <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပင်နံပါတ်ထည့်ပါ။"</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် စကားဝှက်ထည့်ပါ။"</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"အပ်ဒိတ်လုပ်ထားသည်။ ရှေ့ဆက်ရန် ပုံဖော်ရေးဆွဲပါ။"</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 6e402d41cdf1..af24d4002e0e 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -126,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Teken het patroon om de update later te installeren"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Apparaat geüpdatet. Voer de pincode in om door te gaan."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Apparaat geüpdatet. Voer het wachtwoord in om door te gaan."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken het patroon om door te gaan."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Apparaat geüpdatet. Teken patroon om door te gaan."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index f709aec1a5ea..ead092099326 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -126,5 +126,5 @@
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Desenează pentru a instala actualizarea mai târziu"</string>
<string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Dispozitivul s-a actualizat. Introdu codul PIN pentru a continua."</string>
<string name="kg_prompt_after_update_password" msgid="153703052501352094">"Dispozitivul s-a actualizat. Introdu parola pentru a continua."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitivul s-a actualizat. Desenează modelul pentru a continua."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Dispozitiv actualizat. Desenează modelul și continuă."</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index 807bbcb45bb1..595fba5dfcb2 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -124,7 +124,7 @@
<string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Введите PIN-код, чтобы установить обновление позже."</string>
<string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Введите пароль, чтобы установить обновление позже."</string>
<string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Введите графический ключ, чтобы установить обновление позже."</string>
- <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Введите PIN-код."</string>
- <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Введите пароль."</string>
- <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Введите графический ключ."</string>
+ <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Устройство обновлено. Чтобы продолжить, введите PIN-код."</string>
+ <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Устройство обновлено. Чтобы продолжить, введите пароль."</string>
+ <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Устройство обновлено. Чтобы продолжить, введите графический ключ."</string>
</resources>
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 5a70b79cb176..452bc317e2d5 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -66,7 +66,7 @@
<FrameLayout
android:id="@+id/status_bar_start_side_content"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_gravity="center_vertical|start"
android:clipChildren="false">
@@ -77,7 +77,7 @@
and DISABLE_NOTIFICATION_ICONS, respectively -->
<LinearLayout
android:id="@+id/status_bar_start_side_except_heads_up"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_gravity="center_vertical|start"
android:clipChildren="false">
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index acee4258b64d..7e03bd9d4325 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,24 +26,6 @@
android:layout_height="match_parent"
android:fitsSystemWindows="true">
- <com.android.systemui.statusbar.BackDropView
- android:id="@+id/backdrop"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- sysui:ignoreRightInset="true"
- >
- <ImageView android:id="@+id/backdrop_back"
- android:layout_width="match_parent"
- android:scaleType="centerCrop"
- android:layout_height="match_parent" />
- <ImageView android:id="@+id/backdrop_front"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"
- android:visibility="invisible" />
- </com.android.systemui.statusbar.BackDropView>
-
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_behind"
android:layout_width="match_parent"
@@ -63,7 +45,8 @@
<com.android.systemui.statusbar.LightRevealScrim
android:id="@+id/light_reveal_scrim"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ sysui:ignoreRightInset="true" />
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
@@ -83,6 +66,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
+ <ViewStub
+ android:id="@+id/communal_ui_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
<include layout="@layout/brightness_mirror_container" />
<com.android.systemui.scrim.ScrimView
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 24846d9ae16e..f68dd7b68f42 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Stoor tans skermskoot in werkprofiel …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skermkiekie is gestoor"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kon nie skermkiekie stoor nie"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Toestel moet ontsluit word voordat skermkiekie gestoor kan word"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer weer skermkiekie neem"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan nie skermkiekie stoor nie"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Saai tans uit"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Onbenoemde toestel"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen toestelle beskikbaar nie"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi is nie gekoppel nie"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans vinnig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans stadig • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laai tans • Vol oor <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik die pylknoppie om die gemeenskaplike tutoriaal te begin"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 23def2894368..20a81206f3b9 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ቅጽበታዊ ገፅ እይታን ወደ የስራ መገለጫ በማስቀመጥ ላይ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ቅጽበታዊ ገፅ ዕይታ ተቀምጧል"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ቅጽበታዊ ገፅ ዕይታን ማስቀመጥ አልተቻለም"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ቅጽበታዊ ገፅ ዕይታ ከመቀመጡ በፊት መሣሪያ መከፈት አለበት"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ቅጽበታዊ ገፅ ዕይታን እንደገና ማንሳት ይሞክሩ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ቅጽበታዊ ገፅ እይታን ማስቀመጥ አልተቻለም"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"በመውሰድ ላይ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ያልተሰየመ መሣሪያ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ምንም መሣሪያዎች አይገኙም"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi አልተገናኘም"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በፍጥነት ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • በዝግታ ኃይልን በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ኃይል በመሙላት ላይ • በ<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ውስጥ ይሞላል"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"የጋራ አጋዥ ሥልጠናን ለመጀመር የቀስት አዝራሩ ላይ ጠቅ ያድርጉ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 80d63a23fd94..03e019c5e7ed 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"جارٍ حفظ لقطة الشاشة في الملف الشخصي للعمل…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"تم حفظ لقطة الشاشة."</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"تعذّر حفظ لقطة الشاشة"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"يجب أن يتم فتح قفل الجهاز قبل حفظ لقطة الشاشة."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"جرّب أخذ لقطة الشاشة مرة أخرى"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"يتعذّر حفظ لقطة الشاشة."</string>
@@ -159,7 +161,7 @@
<string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"النقش غير صحيح."</string>
<string name="biometric_dialog_wrong_password" msgid="69477929306843790">"كلمة مرور غير صحيحة"</string>
<string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"تم إجراء عدد كبير جدًا من المحاولات غير الصحيحة.\nأعد المحاولة خلال <xliff:g id="NUMBER">%d</xliff:g> ثانية."</string>
- <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"الطوارئ"</string>
+ <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"طوارئ"</string>
<string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"يُرجى إعادة المحاولة. المحاولة <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> من <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g>"</string>
<string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"سيتم حذف بياناتك"</string>
<string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"عند إدخال نقش غير صحيح في المحاولة التالية، سيتم حذف بيانات هذا الجهاز."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"جارٍ الإرسال"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"جهاز لا يحمل اسمًا"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"لا يتوفر أي جهاز"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏لم يتم الاتصال بشبكة Wi-Fi."</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن سريعًا • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن ببطء • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"انقر على زر السهم لبدء الدليل التوجيهي العام."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index c84577373919..014e3b7d66aa 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"কৰ্মস্থানৰ প্ৰ’ফাইলত স্ক্ৰীনশ্বট ছেভ কৰি থকা হৈছে…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্ৰীনশ্বট ছেভ কৰা হ’ল"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্ৰীনশ্বট ছেভ কৰিব পৰা নগ\'ল"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্ৰীনশ্বট ছেভ কৰিবলৈ ডিভাইচটো আনলক কৰিবই লাগিব"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"স্ক্ৰীনশ্বট আকৌ ল\'বলৈ চেষ্টা কৰক"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্ৰীনশ্বট ছেভ কৰিব নোৱাৰি"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"কাষ্টিং"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নাম নথকা ডিভাইচ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইচ নাই"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ৱাই-ফাইৰ সৈতে সংযোগ হৈ থকা নাই"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্ৰুতগতিৰে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • লাহে লাহে চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চাৰ্জ হৈ আছে • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ত সম্পূৰ্ণ হ’ব"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"সম্প্ৰদায় সম্পৰ্কীয় নিৰ্দেশনা আৰম্ভ কৰিবলৈ কাঁড়চিহ্নৰ বুটামটোত ক্লিক কৰক"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 5aa26bac0687..d34ad9ad7537 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"İş profili skrinşotu saxlanılır…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinşot yadda saxlandı"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinşotu yadda saxlamaq alınmadı"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinşotu saxlamazdan əvvəl cihaz kiliddən çıxarılmalıdır"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skrinşotu yenidən çəkin"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinşotu yadda saxlamaq mümkün olmadı"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Yayım"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Heç bir cihaz əlçatan deyil"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi qoşulu deyil"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sürətlə şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Asta şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj edilir • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> sonra dolacaq"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"İcma təlimatını başlatmaq üçün ox düyməsinə klikləyin"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index cd36884e4740..2b19e3618b12 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Snimak ekrana se čuva na poslovnom profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Čuvanje snimka ekrana nije uspelo"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora da bude otključan da bi snimak ekrana mogao da se sačuva"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probajte da ponovo napravite snimak ekrana"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Čuvanje snimka ekrana nije uspelo"</string>
@@ -101,8 +103,8 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Obrađujemo video snimka ekrana"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Obaveštenje o sesiji snimanja ekrana je aktivno"</string>
<string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Želite da započnete snimanje?"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju dok snimate. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Kada snimate aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Započni snimanje"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Snimaj zvuk"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Zvuk uređaja"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nije dostupan nijedan uređaj"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi nije povezan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo se puni • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Puni se • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do kraja punjenja"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite na dugme sa strelicom da biste započeli zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string>
@@ -418,17 +422,17 @@
<string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Jedna aplikacija"</string>
<string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Delite ili snimite aplikaciju"</string>
<string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Želite da počnete snimanje ili prebacivanje pomoću aplikacije <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Kada delite, snimate ili prebacujete, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Kada delite, snimate ili prebacujete aplikaciju, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Pokreni"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> je onemogućila ovu opciju"</string>
<string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Želite da započnete prebacivanje?"</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Kada prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Kada prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Započni prebacivanje"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Želite da počnete da delite?"</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i video snimcima."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Kada delite, snimate ili prebacujete, Android ima pristup kompletnom sadržaju koji je vidljiv na ekranu ili se pušta na uređaju. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Kada delite, snimate ili prebacujete aplikaciju, Android ima pristup kompletnom sadržaju koji je vidljiv ili se pušta u toj aplikaciji. Zato budite pažljivi sa lozinkama, informacijama o plaćanju, porukama, slikama i audio i videima."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Pokreni"</string>
<string name="media_projection_task_switcher_text" msgid="590885489897412359">"Deljenje se zaustavlja kada menjate aplikacije"</string>
<string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Deli ovu aplikaciju"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 1b03c8ba09f6..301a042319a7 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Захаванне здымка экрана ў працоўны профіль…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Здымак экрана захаваны"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не атрымалася зрабіць здымак экрана"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Перад захаваннем здымка экрана трэба разблакіраваць прыладу"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Паспрабуйце зрабіць здымак экрана яшчэ раз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не ўдалося захаваць здымак экрана"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Ідзе перадача"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Прылада без назвы"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма даступных прылад"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Няма падключэння да Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе хуткая зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе павольная зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ідзе зарадка • Поўны зарад праз <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Націсніце кнопку са стрэлкай, каб азнаёміцца з дапаможнікам"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 4ed1ad944045..29f9d9e9616e 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Екранната снимка се запазва в служебния профил…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Екранната снимка е запазена"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не можа да се запази екранна снимка"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"За да бъде запазена екранната снимка, устройството трябва да бъде отключено"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Опитайте да направите екранна снимка отново"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Екранната снимка не може да се запази"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Предава се"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Устройство без име"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Няма налични устройства"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Не е установена връзка с Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инвертиране на цветовете"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бързо • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се бавно • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарежда се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до пълно зареждане"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликнете върху бутона със стрелка, за да стартирате общия урок"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 426d38d68a0e..c2662d1d7c7e 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"অফিস প্রোফাইলে স্ক্রিনশট সেভ করা হচ্ছে…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"স্ক্রিনশট সেভ করা হয়েছে"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"স্ক্রিনশট সেভ করা যায়নি"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"স্ক্রিনশট সেভ করার আগে ডিভাইসটি অবশ্যই আনলক করতে হবে"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"আবার স্ক্রিনশট নেওয়ার চেষ্টা করুন"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"স্ক্রিনশট সেভ করা যায়নি"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"কাস্ট করা হচ্ছে"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"নামবিহীন ডিভাইস"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"কোনো ডিভাইস উপলব্ধ নয়"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ওয়াই-ফাই কানেক্ট করা নেই"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • দ্রুত চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ধীরে চার্জ হচ্ছে • পুরো চার্জ হতে <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> লাগবে"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • চার্জ হচ্ছে • পুরো চার্জ হতে আরও <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> সময় লাগবে"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"কমিউনিটি টিউটোরিয়াল চালু করতে তীরচিহ্ন বোতামে ক্লিক করুন"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 4eed7b8965ba..8d104546dead 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pohranjivanje snimka ekrana na radni profil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimak ekrana je sačuvan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nije moguće sačuvati snimak ekrana"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Morate otključati uređaj da možete sačuvati snimak ekrana"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo snimiti ekran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće sačuvati snimak ekrana"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prebacivanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovani uređaj"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi mreža nije povezana"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Brzo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sporo punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Punjenje • Potpuna napunjenost za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite na dugme sa strelicom da pokrenete zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index b55a79ad8dd7..683c0344a32e 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"S\'està desant la captura al perfil de treball…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"S\'ha desat la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No s\'ha pogut desar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositiu ha d\'estar desbloquejat abans que la captura de pantalla es pugui desar"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prova de tornar a fer una captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No es pot desar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"En emissió"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositiu sense nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hi ha cap dispositiu disponible."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"La Wi‑Fi no està connectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant ràpidament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregant lentament • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • S\'està carregant • Es completarà d\'aquí a <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fes clic al botó de la fletxa per iniciar el tutorial de la comunitat"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 05b04191f6c7..cbbf9df4e6d3 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukládání snímku obrazovky do pracovního profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snímek obrazovky byl uložen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snímek obrazovky se nepodařilo uložit"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Aby bylo možné uložit screenshot, zařízení musí být odemknuto"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zkuste snímek pořídit znovu"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímek obrazovky se nepodařilo uložit"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Odesílání"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepojmenované zařízení"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nejsou dostupná žádná zařízení"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Není připojena Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Rychlé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Pomalé nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíjení • Plně nabito za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknutím na tlačítko s šipkou spustíte společný výukový program"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 5971034b991c..57124bdee17b 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gemmer screenshot på din arbejdsprofil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshottet blev gemt"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshottet kunne ikke gemmes"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheden skal være låst op, før du kan gemme screenshots"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv at tage et screenshot igen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Dit screenshot kunne ikke gemmes."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Caster"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhed uden navn"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Der er ingen tilgængelige enheder"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Manglende Wi-Fi-forbindelse"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader hurtigt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader langsomt • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Oplader • Fuldt opladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik på pilen for at starte den fælles vejledning"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index c773f71d7786..8c308b9844f7 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot wird in Arbeitsprofil gespeichert…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot gespeichert"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Screenshot konnte nicht gespeichert werden"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Damit Screenshots gespeichert werden können, muss das Gerät entsperrt sein"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Versuche noch einmal, den Screenshot zu erstellen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Screenshot kann nicht gespeichert werden"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Wird übertragen"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unbenanntes Gerät"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Keine Geräte verfügbar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WLAN nicht verbunden"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string>
@@ -395,6 +398,8 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird schnell geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird langsam geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wird geladen • Voll in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <!-- no translation found for communal_tutorial_indicator_text (700342473477865107) -->
+ <skip />
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index b6c95aa50bfd..69d877bbd7ca 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Αποθήκευση στιγμιότ. οθόνης στο προφίλ εργασίας…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Το στιγμιότυπο οθόνης αποθηκεύτηκε"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Μη δυνατή αποθήκευση του στιγμιότυπου οθόνης"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Η συσκευή πρέπει να ξεκλειδωθεί για να αποθηκευτεί το στιγμιότυπο οθόνης."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Δοκιμάστε να κάνετε ξανά λήψη του στιγμιότυπου οθόνης"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Δεν είναι δυνατή η αποθήκευση στιγμιότυπου οθόνης."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Μετάδοση"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ανώνυμη συσκευή"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Δεν υπάρχουν διαθέσιμες συσκευές"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Το Wi-Fi δεν είναι συνδεδεμένο"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Γρήγορη φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Αργή φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Φόρτιση • Πλήρης φόρτιση σε <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Κάντε κλικ στο κουμπί βέλους για να ξεκινήσετε τον κοινόχρηστο οδηγό"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 6c2b2307fccf..645f70ee7a26 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 0e282ff6fc63..f05d3c013987 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -76,6 +76,7 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"External Display"</string>
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +281,7 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"No Wi‑Fi or Ethernet connection"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string>
@@ -395,6 +396,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 6c2b2307fccf..645f70ee7a26 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 6c2b2307fccf..645f70ee7a26 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Saving screenshot to work profile…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot saved"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Couldn\'t save screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Device must be unlocked before screenshot can be saved"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Try taking screenshot again"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Can\'t save screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Unnamed device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No devices available"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi not connected"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging rapidly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging slowly • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Charging • Full in <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Click on the arrow button to start the communal tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 1f3ab27102d2..4934d73fe510 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -76,6 +76,7 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‎‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎‎‎Saving screenshot to work profile…‎‏‎‎‏‎"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‎‎‏‎Screenshot saved‎‏‎‎‏‎"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‎‏‏‏‏‎‏‏‎Couldn\'t save screenshot‎‏‎‎‏‎"</string>
+ <string name="screenshot_failed_external_display_indication" msgid="6555673132061101936">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‎External Display‎‏‎‎‏‎"</string>
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‎‎Device must be unlocked before screenshot can be saved‎‏‎‎‏‎"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‎‏‏‎‎‎‎‏‎Try taking screenshot again‎‏‎‎‏‎"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎Can\'t save screenshot‎‏‎‎‏‎"</string>
@@ -280,7 +281,7 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‎‏‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎Casting‎‏‎‎‏‎"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‎‎Unnamed device‎‏‎‎‏‎"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‎‏‏‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‎No devices available‎‏‎‎‏‎"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‏‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎Wi‑Fi not connected‎‏‎‎‏‎"</string>
+ <string name="quick_settings_cast_no_network" msgid="3863016850468559522">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎No Wi‑Fi or Ethernet connection‎‏‎‎‏‎"</string>
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎Brightness‎‏‎‎‏‎"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎Color inversion‎‏‎‎‏‎"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎Color correction‎‏‎‎‏‎"</string>
@@ -395,6 +396,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‏‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‏‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging rapidly • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging slowly • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="PERCENTAGE">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • Charging • Full in ‎‏‎‎‏‏‎<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‎‏‏‎Click on the arrow button to start the communal tutorial‎‏‎‎‏‎"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‏‏‏‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎Switch user‎‏‎‎‏‎"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎pulldown menu‎‏‎‎‏‎"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎All apps and data in this session will be deleted.‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 52514d22f927..fa1a3f55ce56 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando cap. de pantalla en perfil de trabajo…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Se guardó la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No se pudo guardar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe estar desbloqueado para poder guardar la captura de pantalla"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a hacer una captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se pudo guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitiendo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Red Wi-Fi no conectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corregir colores"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lento • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Se completará en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Haz clic en el botón de flecha para iniciar el instructivo comunal"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 4a3c06412802..005895be7843 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Guardando captura en el perfil de trabajo…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Se ha guardado la captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"No se ha podido guardar la captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"El dispositivo debe desbloquearse para que se pueda guardar la captura de pantalla"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Vuelve a intentar hacer la captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"No se puede guardar la captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Enviando"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sin nombre"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"No hay dispositivos disponibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi no conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga rápida • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • En <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> terminará de cargarse"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • Carga completa en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Haz clic en el botón de la flecha para iniciar el tutorial de la comunidad"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 08b489d11d7e..b4463d21878f 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekraanipildi salvestamine tööprofiilile …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekraanipilt salvestati"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekraanipilti ei õnnestunud salvestada"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enne ekraanipildi salvestamist tuleb seade avada"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Proovige ekraanipilt uuesti jäädvustada"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekraanipilti ei saa salvestada"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Osatäitjad"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimeta seade"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ühtegi seadet pole saadaval"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi-ühendus puudub"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kiirlaadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Aeglane laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laadimine • Täis <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> pärast"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ühise õpetuse käivitamiseks klõpsake noolenuppu"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 19495bccb4f8..6701a7714ee1 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pantaila-argazkia laneko profilean gordetzen…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Gorde da pantaila-argazkia"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ezin izan da gorde pantaila-argazkia"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pantaila-argazkia gordetzeko, gailuak desblokeatuta egon beharko du"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Saiatu berriro pantaila-argazkia ateratzen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ezin da gorde pantaila-argazkia"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Igortzen"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Izenik gabeko gailua"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ez dago gailurik erabilgarri"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ez zaude konektatuta wifi-sarera"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Koloreen alderantzikatzea"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Bizkor kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mantso kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Kargatzen • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> guztiz kargatu arte"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Tutorial komuna hasteko, sakatu geziaren botoia"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 68f6c1d9dd88..705cc7cbecb0 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"درحال ذخیره کردن نماگرفت در نمایه کاری…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"نماگرفت ذخیره شد"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"نماگرفت ذخیره نشد"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"برای ذخیره کردن نماگرفت، قفل دستگاه باید باز باشد"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوباره نماگرفت بگیرید"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"نماگرفت ذخیره نمی‌شود"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"در حال فرستادن"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"دستگاه بدون نام"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"دستگاهی موجود نیست"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏Wi-Fi وصل نیست"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن سریع • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ کردن آهسته • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • درحال شارژ شدن • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> تا شارژ کامل"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"برای شروع آموزش گام‌به‌گام عمومی، روی دکمه جهت‌نما کلیک کنید"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایین‌پر"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامه‌ها و داده‌های این جلسه حذف خواهد شد."</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 934abad4b225..d1c583be44d3 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Kuvakaappausta tallennetaan työprofiiliin…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Kuvakaappaus tallennettu"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kuvakaappauksen tallennus epäonnistui"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Laitteen lukitus täytyy avata ennen kuin kuvakaappaus voidaan tallentaa"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Yritä ottaa kuvakaappaus uudelleen."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kuvakaappausta ei voi tallentaa"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Lähetetään"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nimetön laite"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Laitteita ei ole käytettävissä"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fiä ei ole yhdistetty"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu nopeasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu hitaasti • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Latautuu • Täynnä <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> päästä"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Aloita yhteisöesittely klikkaamalla nuolipainiketta"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index fe90569169d9..366d71568c63 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sauv. de la capture dans le profil prof. en cours…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"L\'appareil doit être déverrouillé avant qu\'une capture d\'écran puisse être enregistrée"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de faire une autre capture d\'écran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil à proximité"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Non connecté au Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"En recharge rapide : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"En recharge lente : <xliff:g id="PERCENTAGE">%2$s</xliff:g> • Terminée <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge en cours… • Se terminera dans <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Cliquez sur le bouton de flèche pour démarrer le tutoriel communautaire"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 27a4bb6a0e1f..8a5109242733 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Enregistrement de capture d\'écran dans profil pro…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Capture d\'écran enregistrée"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossible d\'enregistrer la capture d\'écran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Vous devez déverrouiller l\'appareil pour que la capture d\'écran soit enregistrée"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Essayez de nouveau de faire une capture d\'écran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossible d\'enregistrer la capture d\'écran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Diffusion"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Appareil sans nom"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Aucun appareil disponible."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi non connecté"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge rapide • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge lente • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Recharge • Temps restant : <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Cliquer sur la flèche pour démarrer le tutoriel collectif"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 2056c2fba14b..e8aa65f76327 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Gardando captura de pantalla no perfil de traballo"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Gardouse a captura de pantalla"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Non se puido gardar a captura de pantalla"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que se poida gardar a captura de pantalla, o dispositivo debe estar desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Volve tentar crear unha captura de pantalla"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Non se puido gardar a captura de pantalla"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Emitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sen nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Non hai dispositivos dispoñibles"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"A wifi non está conectada"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando rapidamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando lentamente • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Cargando • A carga completarase en <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fai clic no botón da frecha para iniciar o titorial comunitario"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 84be50a481e8..15b1b44a3e5b 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ઑફિસની પ્રોફાઇલમાં સ્ક્રીનશૉટ સાચવી રહ્યાં છીએ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"સ્ક્રીનશૉટ સાચવ્યો"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"સ્ક્રીનશૉટ સાચવી શક્યાં નથી"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"સ્ક્રીનશૉટ સાચવવામાં આવે તે પહેલાં ડિવાઇસને અનલૉક કરવું જરૂરી છે"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ફરીથી સ્ક્રીનશૉટ લેવાનો પ્રયાસ કરો"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"સ્ક્રીનશૉટ સાચવી શકાતો નથી"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"કાસ્ટ કરી રહ્યાં છે"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"અનામાંકિત ઉપકરણ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"કોઈ ઉપકરણો ઉપલબ્ધ નથી"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"વાઇ-ફાઇ કનેક્ટ નથી"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ઝડપથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ધીમેથી ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં ચાર્જ થઈ જશે"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ચાર્જ થઈ રહ્યું છે • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>માં પૂરું ચાર્જ થઈ જશે"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"કૉમ્યુનલ ટ્યૂટૉરિઅલ શરૂ કરવા માટે ઍરો બટન પર ક્લિક કરો"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 2b0019f5274e..d63f5cb2564f 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"स्क्रीनशॉट, वर्क प्रोफ़ाइल में सेव किया जा रहा है…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव किया गया"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव नहीं किया जा सका"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव करने के लिए डिवाइस का अनलॉक होना ज़रूरी है"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट दोबारा लेने की कोशिश करें"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट को सेव नहीं किया जा सकता"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"कास्टिंग"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"अनाम डिवाइस"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोई डिवाइस उपलब्ध नहीं"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाई-फ़ाई कनेक्ट नहीं है"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • तेज़ चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • धीरे चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हो रहा है • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> में पूरा चार्ज हो जाएगा"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"कम्यूनिटी ट्यूटोरियल शुरू करने के लिए, तीर के निशान वाले बटन पर क्लिक करें"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 23899fcb2110..c3ea5ff2735f 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Spremanje snimke zaslona na poslovni profil…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snimka zaslona spremljena"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snimka zaslona nije spremljena"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Uređaj mora biti otključan da bi se snimka zaslona mogla spremiti"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pokušajte ponovo napraviti snimku zaslona"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nije moguće spremiti snimku zaslona"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Emitiranje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Uređaj bez naziva"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nema dostupnih uređaja"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi mreža nije povezana"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • brzo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • sporo punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • punjenje • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> do napunjenosti"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite gumb sa strelicom da biste pokrenuli zajednički vodič"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index e5b17d9c6517..c89423248576 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Képernyőkép mentése a munkaprofilba…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"A képernyőkép mentése sikerült"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nem sikerült a képernyőkép mentése"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Az eszközt fel kell oldani a képernyőkép mentése előtt"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Próbálja meg újra elkészíteni a képernyőképet"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nem lehetséges a képernyőkép mentése"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Átküldés"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Név nélküli eszköz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nem áll rendelkezésre eszköz"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nem kapcsolódik Wi‑Fi-hálózathoz"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Gyors töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lassú töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Töltés • A teljes töltöttségig: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kattintson a nyíl gombra a közösségi útmutató elindításához"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4a1c291e3d74..67d388638cea 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Սքրինշոթը պահվում է աշխատանքային պրոֆիլում…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Սքրինշոթը պահվեց"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Չհաջողվեց պահել սքրինշոթը"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Սքրինշոթը պահելու համար ապակողպեք սարքը։"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Փորձեք նորից"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Չհաջողվեց պահել սքրինշոթը"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Հեռարձակում"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Անանուն սարք"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Հասանելի սարքեր չկան"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-ը միացված չէ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Արագ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Դանդաղ լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Լիցքավորում • Մնացել է <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Սեղմեք սլաքի կոճակը՝ ուղեցույցը գործարկելու համար"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 18a76686154f..b1feb4876e85 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan screenshot ke profil kerja …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot disimpan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Perangkat harus dibuka kuncinya agar screenshot dapat disimpan"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Coba ambil screenshot lagi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Melakukan transmisi"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Perangkat tanpa nama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Perangkat tak tersedia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi tidak terhubung"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan cepat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya dengan lambat • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengisi daya • Penuh dalam waktu <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik tombol panah untuk memulai tutorial komunal"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 70bed4665e17..9ff9c1a78eb0 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Vistar skjámynd á vinnusnið…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skjámynd vistuð"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekki var hægt að vista skjámynd"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Taka verður tækið úr lás áður en hægt er að vista skjámynd"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prófaðu að taka skjámynd aftur"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekki er hægt að vista skjámynd"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Sendir út"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Ónefnt tæki"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Engin tæki til staðar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ekki tengt"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hraðhleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hæg hleðsla • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Í hleðslu • Full hleðsla eftir <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Smelltu á örvahnappinn til að hefja samfélagsleiðsögnina"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index baffdb9778cd..7fafa624f7ea 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvataggio screenshot nel profilo di lavoro…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot salvato"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Impossibile salvare lo screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"È necessario sbloccare il dispositivo per poter salvare lo screenshot"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Riprova ad acquisire lo screenshot"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Impossibile salvare lo screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"In trasmissione"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo senza nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nessun dispositivo disponibile"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nessuna connessione Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica veloce • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ricarica lenta • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • In carica • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> alla ricarica completa"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Fai clic sul pulsante Freccia per iniziare il tutorial della community"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 968a98287275..ced5895ddf2c 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"צילום המסך נשמר בפרופיל העבודה…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"צילום המסך נשמר"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"לא ניתן היה לשמור את צילום המסך"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"כדי שצילום המסך יישמר, צריך לבטל את הנעילה של המכשיר"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"אפשר לצלם שוב את המסך"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"לא ניתן לשמור את צילום המסך"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"‏מתבצעת העברה (cast)"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"מכשיר ללא שם"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"אין מכשירים זמינים"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏אין חיבור ל-Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה מהירה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה איטית • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • בטעינה • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> עד לסיום"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"אפשר ללחוץ על לחצן החץ כדי להפעיל את המדריך המשותף"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"החלפת משתמש"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"תפריט במשיכה למטה"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"כל האפליקציות והנתונים בסשן הזה יימחקו."</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 798d42add510..f5d5b74ea49a 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"スクリーンショットを仕事用プロファイルに保存中…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"スクリーンショットを保存しました"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"スクリーンショット保存エラー"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"スクリーンショットを保存するには、デバイスのロックを解除する必要があります"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"スクリーンショットを撮り直してください"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"スクリーンショットを保存できません"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"キャストしています"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"名前のないデバイス"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"利用可能なデバイスがありません"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi 未接続"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 急速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 低速充電中 • 完了まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • フル充電まで <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"矢印ボタンをクリックすると、コミュニティ チュートリアルが開始します"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index f21a2a42536b..ae93966a83a0 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"მიმდინარეობს ეკრანის ანაბეჭდის შენახვა სამუშაო პროფილში…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ეკრანის ანაბეჭდი შენახულია"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ეკრანის ანაბეჭდის შენახვა ვერ მოხერხდა"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"მოწყობილობა უნდა განიბლოკოს ეკრანის ანაბეჭდის შენახვა რომ შეძლოთ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ხელახლა ცადეთ ეკრანის ანაბეჭდის გაკეთება"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ეკრანის ანაბეჭდის შენახვა ვერ ხერხდება"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"გადაიცემა"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"უსახელო მოწყობილობა"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"მოწყობილობები მიუწვდომელია"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi არ არის დაკავშირებული"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • სწრაფად იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ნელა იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • იტენება • სრულ დატენვამდე <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"დააწკაპუნეთ ისრის ღილაკზე, რათა დაიწყოთ საერთო სახელმძღვანელო"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 746c02ed780f..8c8efd156bc4 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жұмыс профиліне сақталып жатыр…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сақталды"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сақталмады"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншот сақталуы үшін, құрылғы құлпын ашу керек."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Қайта скриншот жасап көріңіз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншотты сақтау мүмкін емес."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Трансляциялануда"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Атаусыз құрылғы"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Құрылғылар қол жетімді емес"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi желісіне жалғанбаған"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жылдам зарядтау • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Баяу зарядталуда • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядталып жатыр. • Толуына <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> қалды."</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Жалпы оқулықты ашу үшін бағыт түймесін басыңыз."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 9f3b99121b19..cefdd2759c19 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"កំពុងរក្សាទុករូបថតអេក្រង់ទៅកម្រងព័ត៌មានការងារ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"បានរក្សាទុក​រូបថតអេក្រង់"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"មិន​អាច​រក្សាទុក​រូបថត​អេក្រង់បានទេ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ត្រូវតែ​ដោះសោ​ឧបករណ៍​ជាមុនសិន ទើបអាច​រក្សាទុក​រូបថតអេក្រង់​បាន"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"សាកល្បង​ថតរូបថត​អេក្រង់​ម្តងទៀត"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"មិនអាច​រក្សាទុករូបថត​អេក្រង់បានទេ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ការ​ចាត់​ថ្នាក់"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ឧបករណ៍​​ដែល​មិន​មាន​ឈ្មោះ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"មិន​មាន​ឧបករណ៍​ដែល​អាច​ប្រើ​បាន"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"មិនមាន​ការតភ្ជាប់ Wi-Fi ទេ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាស​ពណ៌"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការ​កែតម្រូវ​ពណ៌"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុង​សាកថ្មយ៉ាង​ឆាប់រហ័ស • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុង​សាកថ្ម​យឺត • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • កំពុងសាកថ្ម • ពេញក្នុងរយៈពេល <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ចុចលើប៊ូតុងសញ្ញាព្រួញ ដើម្បីចាប់ផ្ដើមមេរៀនសហគមន៍"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរ​អ្នក​ប្រើ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយ​ទាញចុះ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យ​ទាំងអស់​ក្នុង​វគ្គ​នេះ​នឹង​ត្រូវ​លុប។"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 79eef7295bc0..6ec02cad43b1 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ಗೆ ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಉಳಿಸಲಾಗುತ್ತಿದೆ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಅನ್ನು ಉಳಿಸಲಾಗಿದೆ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸುವ ಮೊದಲು ಸಾಧನವನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಬೇಕು"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಪುನಃ ತೆಗೆದುಕೊಳ್ಳಲು ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ಹೆಸರಿಸದಿರುವ ಸಾಧನ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ಯಾವುದೇ ಸಾಧನಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ವೈ-ಫೈ ಸಂಪರ್ಕಗೊಂಡಿಲ್ಲ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್‍ವರ್ಶನ್"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ವೇಗವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ನಿಧಾನವಾಗಿ ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ಚಾರ್ಜ್ ಆಗುತ್ತಿದೆ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ದಲ್ಲಿ ಪೂರ್ಣಗೊಳ್ಳುತ್ತದೆ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ಸಮುದಾಯದ ಟುಟೋರಿಯಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಆ್ಯರೋ ಬಟನ್ ಅನ್ನು ಕ್ಲಿಕ್ ಮಾಡಿ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್‌ಡೌನ್ ಮೆನು"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಷನ್‌ನಲ್ಲಿನ ಎಲ್ಲ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 7985e4c7e3af..69525ee1d360 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"직장 프로필에 스크린샷 저장 중…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"스크린샷 저장됨"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"스크린샷을 저장할 수 없음"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"스크린샷을 저장하려면 기기를 잠금 해제해야 합니다."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"스크린샷을 다시 찍어 보세요."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"스크린샷을 저장할 수 없습니다."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"전송 중"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"이름이 없는 기기"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"사용 가능한 기기가 없습니다."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi가 연결되지 않음"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 고속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 저속 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 충전 중 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> 후 충전 완료"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"공동 튜토리얼을 시작하려면 화살표 버튼을 클릭하세요."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index ded1f752ef87..400afed0da76 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Скриншот жумуш профилине сакталууда…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сакталды"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Скриншот сакталган жок"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Скриншотту сактоо үчүн түзмөктүн кулпусун ачуу керек"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Скриншотту кайра тартып көрүңүз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Скриншот сакталган жок"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Тышкы экранга чыгарылууда"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Аты жок түзмөк"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Жеткиликтүү түзмөктөр жок"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi туташкан жок"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстөрдү инверсиялоо"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түстөрдү тууралоо"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Тез кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Жай кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Кубатталууда • Толгонго чейин <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> калды"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Жалпы үйрөткүчтү иштетүү үчүн жебе баскычын басыңыз"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 587caaf3ecf3..db526b187d38 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -46,4 +46,7 @@
For now, this value has effect only when flag lockscreen.enable_landscape is enabled.
TODO (b/293252410) - change this comment/resource when flag is enabled -->
<bool name="force_config_use_split_notification_shade">true</bool>
+
+ <!-- Whether to show bottom sheets edge to edge -->
+ <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 1e61b8a85fb1..b66e6dc84956 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ກຳລັງບັນທຶກຮູບໜ້າຈໍໃສ່ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ບັນທຶກຮູບໜ້າຈໍໄວ້ແລ້ວ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ຈະຕ້ອງປົດລັອກອຸປະກອນກ່ອນບັນທຶກຮູບໜ້າຈໍ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ກະລຸນາລອງຖ່າຍຮູບໜ້າຈໍອີກຄັ້ງ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ບໍ່ສາມາດບັນທຶກຮູບໜ້າຈໍໄດ້"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"​ກຳ​ລັງ​ສົ່ງ​ສັນ​ຍານ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"​ອຸ​ປະ​ກອນບໍ່​ມີ​ຊື່"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"​ບໍ່​ມີ​ອຸ​ປະ​ກອນ​ທີ່​ສາ​ມາດ​ໃຊ້​ໄດ້"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ບໍ່ໄດ້ເຊື່ອມຕໍ່ Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບໄວ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟແບບຊ້າ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ກຳລັງສາກໄຟ • ຈະເຕັມໃນອີກ <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ຄລິກໃສ່ປຸ່ມລູກສອນເພື່ອເລີ່ມຕົ້ນສອນການນຳໃຊ້ຊຸມຊົນ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯ​ແລະ​ຂໍ້​ມູນ​ທັງ​ໝົດ​ໃນ​ເຊດ​ຊັນ​ນີ້​ຈະ​ຖືກ​ລຶບ​ອອກ."</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 06c1369d29ad..955fe6713d2e 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Išsaugoma ekrano kopija darbo profilyje…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrano kopija išsaugota"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrano kopijos išsaugoti nepavyko"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Įrenginys turi būti atrakintas, kad būtų galima išsaugoti ekrano kopiją"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Pabandykite padaryti ekrano kopiją dar kartą"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekrano kopijos išsaugoti nepavyko"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Perduodama"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Įrenginys be pavadinimo"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nėra pasiekiamų įrenginių"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"„Wi-Fi“ neprijungtas"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sparčiai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lėtai įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Įkraunama • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> iki visiško įkrovimo"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Spustelėkite rodyklės mygtuką, kad paleistumėte bendruomenės mokomąją medžiagą"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index fdf30285cee4..16cef4b74288 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Notiek ekrānuzņēmuma saglabāšana darba profilā…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekrānuzņēmums saglabāts"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekrānuzņēmumu neizdevās saglabāt."</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Lai varētu saglabāt ekrānuzņēmumu, ierīcei ir jābūt atbloķētai."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Mēģiniet izveidot jaunu ekrānuzņēmumu."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nevar saglabāt ekrānuzņēmumu"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Notiek apraide…"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nenosaukta ierīce"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nav pieejamu ierīču."</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Nav izveidots savienojums ar Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ātrā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lēnā uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Notiek uzlāde • Laiks līdz pilnai uzlādei: <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Noklikšķiniet uz bultiņas pogas, lai palaistu kopienas pamācību."</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 80ef7bb64d07..8babe8c1776f 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Се зачувува слика од екранот на вашиот работен профил…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Сликата од екранот е зачувана"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не може да се зачува слика од екранот"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уредот мора да биде отклучен за да може да се зачува слика од екранот"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Повторно обидете се да направите слика од екранот"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не може да се зачува слика од екранот"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Емитување"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименуван уред"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нема достапни уреди"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нема Wi-Fi врска"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни брзо • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни бавно • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Се полни • Полна по <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликнете на копчето со стрелка за да го започнете заедничкото упатство"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Промени го корисникот"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"паѓачко мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Сите апликации и податоци во сесијата ќе се избришат."</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5b1c80f37a18..c5b65366f98a 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ഔദ്യോഗിക പ്രൊഫൈലിലേക്ക് സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നു…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"സ്‌ക്രീൻഷോട്ട് സംരക്ഷിച്ചു"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"സ്‌ക്രീൻഷോട്ട് സംരക്ഷിക്കാനായില്ല"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"സ്ക്രീൻഷോട്ട് സംരക്ഷിക്കുന്നതിന് മുമ്പ് ഉപകരണം അൺലോക്ക് ചെയ്തിരിക്കണം"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"സ്‌ക്രീൻഷോട്ട് എടുക്കാൻ വീണ്ടും ശ്രമിക്കുക"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"സ്‌ക്രീൻഷോട്ട് സംരക്ഷിക്കാനാകുന്നില്ല"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"കാസ്റ്റുചെയ്യുന്നു"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"പേരിടാത്ത ഉപകരണം"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ഉപകരണങ്ങളൊന്നും ലഭ്യമല്ല"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"വൈഫൈ കണക്റ്റ് ചെയ്‌തിട്ടില്ല"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • വേഗത്തിൽ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • പതുക്കെ ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ചാർജ് ചെയ്യുന്നു • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-ൽ പൂർത്തിയാകും"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"കമ്മ്യൂണൽ ട്യൂട്ടോറിയൽ ആരംഭിക്കാൻ അമ്പടയാള ബട്ടണിൽ ക്ലിക്ക് ചെയ്യുക"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 3f65931f276e..b083c663bf19 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Дэлгэцийн агшныг ажлын профайлд хадгалж байна…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Дэлгэцээс дарсан зургийг хадгалсан"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Дэлгэцээс дарсан зургийг хадгалж чадсангүй"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Дэлгэцийн агшныг хадгалах боломжтой болохоос өмнө төхөөрөмжийн түгжээг тайлах ёстой"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Дэлгэцийн зургийг дахин дарж үзнэ үү"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Дэлгэцийн агшныг хадгалах боломжгүй"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Дамжуулж байна"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Нэргүй төхөөрөмж"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Төхөөрөмж байхгүй"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi-д холбогдоогүй байна"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө хувиргалт"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгө тохируулга"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Хурдтай цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Удаан цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Цэнэглэж байна • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>-н дараа дүүрнэ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Нийтийн практик хичээлийг эхлүүлэхийн тулд суман товчийг товшино уу"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 238aacab4d32..5d9f67ea4271 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलवर स्क्रीनशॉट सेव्ह करत आहे…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रीनशॉट सेव्ह केला"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रीनशॉट सेव्ह करू शकलो नाही"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"स्क्रीनशॉट सेव्ह करण्याआधी डिव्हाइस अनलॉक करणे आवश्यक आहे"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रीनशॉट पुन्हा घेण्याचा प्रयत्न करा"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रीनशॉट सेव्ह करू शकत नाही"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"कास्ट करत आहे"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"निनावी डिव्हाइस"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कोणतेही डिव्हाइसेस उपलब्ध नाहीत"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"वाय-फाय नाही"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • वेगाने चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • हळू चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज होत आहे • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मध्ये पूर्ण होईल"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"सामुदायिक ट्यूटोरियल सुरू करण्यासाठी ॲरो बटणावर क्लिक करा"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अ‍ॅप्स आणि डेटा हटवला जाईल."</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 8c764f1e7f75..10e9859ce1af 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Menyimpan tangkapan skrin ke profil kerja…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Tangkapan skrin disimpan"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Tidak dapat menyimpan tangkapan skrin"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Peranti mesti dibuka kunci sebelum tangkapan skrin dapat disimpan"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Cuba ambil tangkapan skrin sekali lagi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Tidak dapat menyimpan tangkapan skrin"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Menghantar"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Peranti tidak bernama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Tiada peranti tersedia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tidak disambungkan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan cepat • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas dengan perlahan • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mengecas • Penuh dalam masa <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik butang anak panah untuk memulakan tutorial umum"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 883b9e95144a..dddc2faa00a9 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"အလုပ်ပရိုဖိုင်တွင် ဖန်သားပြင်ဓာတ်ပုံ သိမ်းနေသည်…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်းပြီးပါပြီ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"မျက်နှာပြင်ပုံကို သိမ်း၍မရပါ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ဖန်သားပြင်ဓာတ်ပုံကို မသိမ်းမီ စက်ပစ္စည်းကို လော့ခ်ဖွင့်ထားရမည်"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"မျက်နှာပြင်ပုံကို ထပ်ရိုက်ကြည့်ပါ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ဖန်သားပြင်ဓာတ်ပုံကို သိမ်း၍မရပါ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ကာစ်တင်"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"အမည်မတပ် ကိရိယာ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ကိရိယာများ မရှိ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi ချိတ်ဆက်ထားခြင်းမရှိပါ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အမြန်အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • နှေးကွေးစွာ အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • အားသွင်းနေသည် • အားပြည့်ရန် <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> လိုသည်"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"အများသုံးရှင်းလင်းပို့ချချက် စတင်ရန် မြားခလုတ်ကို နှိပ်ပါ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 53a4c527cca6..ff32177e7f07 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Lagrer skjermdumpen i jobbprofilen …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skjermdumpen er lagret"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kunne ikke lagre skjermdump"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Enheten må være låst opp før skjermdumpen kan lagres"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Prøv å ta skjermdump på nytt"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan ikke lagre skjermdumpen"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casting"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Enhet uten navn"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ingen enheter er tilgjengelige"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi er ikke tilkoblet"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader raskt • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader sakte • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Lader • Fulladet om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klikk på pilen for å starte fellesveiledningen"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 3b31b3adfd64..61e52bd01cd3 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"कार्य प्रोफाइलमा स्क्रिनसट सेभ गरिँदै छ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"स्क्रिनसट सेभ गरियो"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"डिभाइस अनलक गरेपछि मात्र स्क्रिनसट सुरक्षित गर्न सकिन्छ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"स्क्रिनसट फेरि लिएर हेर्नुहोस्"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"स्क्रिनसट सुरक्षित गर्न सकिएन"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"प्रसारण गर्दै"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"बेनाम उपकरण"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"कुनै उपकरणहरू उपलब्ध छैन"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi कनेक्ट गरिएको छैन"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"कलर करेक्सन"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • छिटो चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • बिस्तारै चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा पूरै चार्ज हुन्छ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • चार्ज हुँदै छ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> मा फुल चार्ज हुने छ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"कम्युनल ट्युटोरियल सुरु गर्न एरो बटनमा क्लिक गर्नुहोस्"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 664af5ec84eb..7d21874133db 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Screenshot opslaan in werkprofiel…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Screenshot opgeslagen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Kan screenshot niet opslaan"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Je moet het apparaat ontgrendelen voordat het screenshot kan worden opgeslagen"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Probeer opnieuw een screenshot te maken"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Kan screenshot niet opslaan"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Casten"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Naamloos apparaat"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Geen apparaten beschikbaar"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wifi niet verbonden"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Snel opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Langzaam opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Opladen • Vol over <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klik op de pijl om de communitytutorial te starten"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index db9e0c5debd8..38039cbc968f 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ୱାର୍କ ପ୍ରୋଫାଇଲରେ ସ୍କ୍ରିନସଟ ସେଭ କରାଯାଉଛି…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ସ୍କ୍ରୀନଶଟ୍ ସେଭ୍ ହୋଇଛି"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ସ୍କ୍ରୀନ୍‍ଶଟ୍ ସେଭ୍ କରିହେବ ନାହିଁ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ସ୍କ୍ରିନସଟ୍ ସେଭ୍ କରିବା ପୂର୍ବରୁ ଡିଭାଇସକୁ ଅନଲକ୍ କରାଯିବା ଆବଶ୍ୟକ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ପୁଣିଥରେ ସ୍କ୍ରୀନ୍‌ଶଟ୍ ନେବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ସ୍କ୍ରିନସଟକୁ ସେଭ୍ କରାଯାଇପାରିବ ନାହିଁ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"କାଷ୍ଟିଙ୍ଗ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ନାମହୀନ ଡିଭାଇସ୍‍"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"କୌଣସି ଡିଭାଇସ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ୱାଇ-ଫାଇ ସଂଯୋଜିତ ହୋଇନାହିଁ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"କଲର ଇନଭର୍ସନ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଶୀଘ୍ର ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଧୀରେ ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ଚାର୍ଜ ହେଉଛି • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>ରେ ସମ୍ପୂର୍ଣ୍ଣ ହେବ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"କମ୍ୟୁନାଲ ଟ୍ୟୁଟୋରିଆଲ ଆରମ୍ଭ କରିବା ପାଇଁ ତୀର ବଟନରେ କ୍ଲିକ କରନ୍ତୁ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍‍ ବଦଳାନ୍ତୁ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍‌ ଓ ଡାଟା ଡିଲିଟ୍‌ ହୋଇଯିବ।"</string>
@@ -660,7 +664,7 @@
<string name="keyboard_shortcut_group_system_recents" msgid="8628108256824616927">"ବର୍ତ୍ତମାନର"</string>
<string name="keyboard_shortcut_group_system_back" msgid="1055709713218453863">"ଫେରନ୍ତୁ"</string>
<string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"ବିଜ୍ଞପ୍ତି"</string>
- <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀ\'ବୋର୍ଡ ସର୍ଟକଟ୍"</string>
+ <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
<string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"କୀ\'ବୋର୍ଡ୍‍ର ଲେଆଉଟ୍‍କୁ ବଦଳାନ୍ତୁ"</string>
<string name="keyboard_shortcut_clear_text" msgid="4679927133259287577">"ଟେକ୍ସଟ ଖାଲି କରନ୍ତୁ"</string>
<string name="keyboard_shortcut_search_list_title" msgid="1156178106617830429">"ସର୍ଟକଟଗୁଡ଼ିକ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 591c5e781382..9011855d20f0 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਰੱਖਿਅਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਕੀਤੇ ਜਾਣ ਤੋਂ ਪਹਿਲਾਂ ਡੀਵਾਈਸ ਨੂੰ ਅਣਲਾਕ ਕੀਤਾ ਹੋਣਾ ਲਾਜ਼ਮੀ ਹੈ"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਦੁਬਾਰਾ ਲੈ ਕੇ ਦੇਖੋ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ਸਕ੍ਰੀਨਸ਼ਾਟ ਨੂੰ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ਕਾਸਟਿੰਗ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"ਬਿਨਾਂ ਨਾਮ ਦਾ ਡੀਵਾਈਸ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ਕੋਈ ਡਿਵਾਈਸਾਂ ਉਪਲਬਧ ਨਹੀਂ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ਵਾਈ-ਫਾਈ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਤੇਜ਼ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਹੌਲੀ ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ਚਾਰਜ ਹੋ ਰਿਹਾ ਹੈ • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> ਵਿੱਚ ਪੂਰਾ ਚਾਰਜ ਹੋਵੇਗਾ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"ਭਾਈਚਾਰਕ ਟਿਊਟੋਰੀਅਲ ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਤੀਰ ਬਟਨ \'ਤੇ ਕਲਿੱਕ ਕਰੋ"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਏਗਾ।"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 195122113140..9b2b3cfa8acd 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Zapisuję zrzut ekranu w profilu służbowym…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Zrzut ekranu został zapisany"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nie udało się zapisać zrzutu ekranu"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Przed zapisaniem zrzutu ekranu musisz odblokować urządzenie"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Spróbuj jeszcze raz wykonać zrzut ekranu"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nie można zapisać zrzutu ekranu."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Przesyłam"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Urządzenie bez nazwy"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Brak dostępnych urządzeń"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Brak połączenia z Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Szybkie ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Wolne ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ładowanie • Pełne naładowanie za <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknij przycisk strzałki, aby uruchomić samouczek społecznościowy"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 693a3a1b7030..a2b1fdbadbd7 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index c4f1f6790907..373cafbb13a5 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"A guardar captura de ecrã no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de ecrã guardada"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Não foi possível guardar a captura de ecrã"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"É necessário desbloquear o dispositivo para guardar a captura de ecrã"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Experimente voltar a efetuar a captura de ecrã."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não é possível guardar a captura de ecrã."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmissão"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Sem dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não ligado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção da cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar rapidamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar lentamente • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • A carregar • Carga completa em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial coletivo"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 693a3a1b7030..a2b1fdbadbd7 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Salvando captura de tela no perfil de trabalho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captura de tela salva"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Falha ao salvar a captura de tela"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Para que a captura de tela seja salva, o dispositivo precisa ser desbloqueado"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tente fazer a captura de tela novamente"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Não foi possível salvar a captura de tela"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Transmitindo"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispositivo sem nome"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Não há dispositivos disponíveis"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi não conectado"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregamento rápido • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carga lenta • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Carregando • Conclusão em <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Clique no botão de seta para iniciar o tutorial compartilhado"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index a942332e2472..1aabc3e3233e 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Se salvează captura în profilul de serviciu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Captură de ecran salvată"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Nu s-a putut salva captura de ecran"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pentru a salva captura de ecran, trebuie să deblochezi dispozitivul"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Încearcă să faci din nou o captură de ecran"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Nu se poate salva captura de ecran"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Se proiectează"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Dispozitiv nedenumit"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Niciun dispozitiv disponibil"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Rețeaua Wi-Fi nu este conectată"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă rapid • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă lent • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Se încarcă • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> până la încărcarea completă"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Dă clic pe butonul săgeată pentru a începe tutorialul pentru comunitate"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 68601d6a71ce..35b4c29407bd 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Сохранение скриншота в рабочем профиле…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Скриншот сохранен"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не удалось сохранить скриншот"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Чтобы сохранить скриншот, разблокируйте устройство."</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Попробуйте сделать скриншот снова."</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не удалось сохранить скриншот."</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Передача изображения"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Безымянное устройство"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Нет доступных устройств"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Нет подключения к сети Wi-Fi."</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Быстрая зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Медленная зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Зарядка • Осталось <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Нажмите кнопку со стрелкой, чтобы ознакомиться с руководством"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 86cb3c324143..f2dd42124cf8 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"කාර්යාල පැතිකඩ වෙත තිර රුව සුරකිමින්…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"තිර රුව සුරකින ලදී"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"තිර රුව සුරැකිය නොහැකි විය"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"තිර රුව සුරැකීමට පෙර උපාංගය අගුලු හැරිය යුතුය"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"තිර රුව නැවත ගැනීමට උත්සාහ කරන්න"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"තිර රුව සුරැකීමට නොහැකිය"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"කාස්ට් කිරීම"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"නම් නොකළ උපාංගය"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"උපාංග නොතිබේ"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi සම්බන්ධ නොවීය"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • වේගයෙන් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • සෙමින් ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ආරෝපණය වෙමින් • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>කින් සම්පූර්ණ වේ"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"වාර්ගික නිබන්ධනය ආරම්භ කිරීමට ඊතල බොත්තම ක්ලික් කරන්න"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 56b9f7549038..844f30ce7ba0 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ukladá sa snímka obrazovky do pracovného profilu…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Snímka obrazovky bola uložená"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Snímku obrazovky sa nepodarilo uložiť"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred uložením snímky obrazovky je potrebné zariadenie odomknúť"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Skúste snímku urobiť znova"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Snímka obrazovky sa nedá uložiť"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Prenáša sa"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nepomenované zariadenie"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nie sú k dispozícii žiadne zariadenia"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Sieť Wi‑Fi nie je pripojená"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzia farieb"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Úprava farieb"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa rýchlo • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa pomaly • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nabíja sa • Do úplného nabitia zostáva <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ak chcete spustiť komunitný návod, kliknite na tlačidlo so šípkou"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index b72f750eae7c..1eab754b5de3 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Shranjevanje posnetka zaslona v delovni profil …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Posnetek zaslona je shranjen"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Posnetka zaslona ni bilo mogoče shraniti"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pred shranjevanjem posnetka zaslona morate odkleniti napravo"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Poskusite znova ustvariti posnetek zaslona"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Posnetka zaslona ni mogoče shraniti"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Predvajanje"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Neimenovana naprava"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Na voljo ni nobene naprave"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Povezava Wi-Fi ni vzpostavljena"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svetlost"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija barv"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Popravljanje barv"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hitro polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Počasno polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Polnjenje • Napolnjeno čez <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliknite gumb s puščico, da zaženete vadnico za skupnost"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 92f6f9c27e77..cafd6a00cff3 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Pamja e ekranit po ruhet te profili i punës…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Pamja e ekranit u ruajt"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Pamja e ekranit nuk mund të ruhej"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Pajisja duhet të shkyçet para se të mund të ruhet pamja e ekranit"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Provo ta nxjerrësh përsëri pamjen e ekranit"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Pamja e ekranit nuk mund të ruhet"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Po transmeton"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Pajisje e paemërtuar"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Nuk ofrohet për përdorim asnjë pajisje"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi nuk është lidhur"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet shpejt • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet ngadalë • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Po karikohet • Plot për <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Kliko mbi butonin e shigjetës për të filluar udhëzuesin e përbashkët"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 7c0d9be76436..8c1450a15219 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Снимак екрана се чува на пословном профилу…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Снимак екрана је сачуван"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Чување снимка екрана није успело"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Уређај мора да буде откључан да би снимак екрана могао да се сачува"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Пробајте да поново направите снимак екрана"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Чување снимка екрана није успело"</string>
@@ -101,8 +103,8 @@
<string name="screenrecord_background_processing_label" msgid="7244617554884238898">"Обрађујемо видео снимка екрана"</string>
<string name="screenrecord_channel_description" msgid="4147077128486138351">"Обавештење о сесији снимања екрана је активно"</string>
<string name="screenrecord_permission_dialog_title" msgid="303380743267672953">"Желите да започнете снимање?"</string>
- <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="4152602778470789965">"Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају док снимате. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="screenrecord_permission_dialog_warning_single_app" msgid="6818309727772146138">"Када снимате апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="screenrecord_permission_dialog_continue" msgid="5811122652514424967">"Започни снимање"</string>
<string name="screenrecord_audio_label" msgid="6183558856175159629">"Снимај звук"</string>
<string name="screenrecord_device_audio_label" msgid="9016927171280567791">"Звук уређаја"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Пребацивање"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Неименовани уређај"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Није доступан ниједан уређај"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"WiFi није повезан"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Брзо се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Споро се пуни • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Пуни се • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до краја пуњења"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Кликните на дугме са стрелицом да бисте започели заједнички водич"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string>
@@ -418,17 +422,17 @@
<string name="screen_share_permission_dialog_option_single_app" msgid="4350961814397220929">"Једна апликација"</string>
<string name="screen_share_permission_app_selector_title" msgid="1404878013670347899">"Делите или снимите апликацију"</string>
<string name="media_projection_entry_app_permission_dialog_title" msgid="9155535851866407199">"Желите да почнете снимање или пребацивање помоћу апликације <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g>?"</string>
- <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_entire_screen" msgid="8736391633234144237">"Када делите, снимате или пребацујете, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_app_permission_dialog_warning_single_app" msgid="5211695779082563959">"Када делите, снимате или пребацујете апликацију, <xliff:g id="APP_SEEKING_PERMISSION">%s</xliff:g> има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_app_permission_dialog_continue" msgid="295463518195075840">"Покрени"</string>
<string name="media_projection_entry_app_permission_dialog_single_app_disabled" msgid="8999903044874669995">"Апликација <xliff:g id="APP_NAME">%1$s</xliff:g> је онемогућила ову опцију"</string>
<string name="media_projection_entry_cast_permission_dialog_title" msgid="8860150223172993547">"Желите да започнете пребацивање?"</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_entire_screen" msgid="1986212276016817231">"Када пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_cast_permission_dialog_warning_single_app" msgid="9900961380294292">"Када пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_cast_permission_dialog_continue" msgid="7209890669948870042">"Започни пребацивање"</string>
<string name="media_projection_entry_generic_permission_dialog_title" msgid="4519802931547483628">"Желите да почнете да делите?"</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
- <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видео снимцима."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_entire_screen" msgid="5407906851409410209">"Када делите, снимате или пребацујете, Android има приступ комплетном садржају који је видљив на екрану или се пушта на уређају. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
+ <string name="media_projection_entry_generic_permission_dialog_warning_single_app" msgid="3454859977888159495">"Када делите, снимате или пребацујете апликацију, Android има приступ комплетном садржају који је видљив или се пушта у тој апликацији. Зато будите пажљиви са лозинкама, информацијама о плаћању, порукама, сликама и аудио и видеима."</string>
<string name="media_projection_entry_generic_permission_dialog_continue" msgid="8640381403048097116">"Покрени"</string>
<string name="media_projection_task_switcher_text" msgid="590885489897412359">"Дељење се зауставља када мењате апликације"</string>
<string name="media_projection_task_switcher_action_switch" msgid="8682258717291921123">"Дели ову апликацију"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 323ce11f853d..59bb1f5372b0 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sparar skärmbild i jobbprofilen …"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skärmbilden har sparats"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Det gick inte att spara skärmbilden"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skärmbilden kan bara sparas om enheten är upplåst"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Testa att ta en skärmbild igen"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Det gick inte att spara skärmbilden"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Castar"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Namnlös enhet"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Inga tillgängliga enheter"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Ej ansluten till wifi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas snabbt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas långsamt • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Laddas • Fulladdat om <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Klicka på pilknappen för att börja med gruppguiden"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 304910a46526..b977dc4e5030 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Inahifadhi picha ya skrini kwenye wasifu wa kazini…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Imehifadhi picha ya skrini"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Imeshindwa kuhifadhi picha ya skrini"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ni sharti ufungue kifaa kabla ya kuhifadhi picha ya skrini"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Jaribu kupiga picha ya skrini tena"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Imeshindwa kuhifadhi picha ya skrini"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Inatuma"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Kifaa hakina jina"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Hakuna vifaa vilivyopatikana"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi haijaunganishwa"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji kwa kasi • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji polepole • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Inachaji • Itajaa baada ya <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Bofya kwenye kitufe cha kishale ili kuanzisha mafunzo ya jumuiya"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 3ac5e2e7c8cc..3d05824c6cc6 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"பணிக் கணக்கில் ஸ்கிரீன்ஷாட் சேமிக்கப்படுகிறது…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"ஸ்கிரீன்ஷாட் சேமிக்கப்பட்டது"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"ஸ்கிரீன் ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ஸ்கிரீன்ஷாட் சேமிக்கப்படுவதற்கு முன்பு சாதனம் அன்லாக் செய்யப்பட வேண்டும்"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ஸ்கிரீன் ஷாட்டை மீண்டும் எடுக்க முயலவும்"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ஸ்கிரீன்ஷாட்டைச் சேமிக்க முடியவில்லை"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"அனுப்புகிறது"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"பெயரிடப்படாத சாதனம்"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"சாதனங்கள் இல்லை"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"வைஃபை இணைக்கப்படவில்லை"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்‌ஷன்"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • வேகமாகச் சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • மெதுவாக சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுதும் சார்ஜாகும்"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • சார்ஜாகிறது • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> இல் முழுவதும் சார்ஜாகும்"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"சமூகப் பயிற்சியைத் தொடங்க அம்புக்குறி பட்டனைக் கிளிக் செய்யுங்கள்"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 0526f95a8e84..6d68b63e7c2d 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"స్క్రీన్‌షాట్‌ను వర్క్ ప్రొఫైల్‌కు సేవ్ చేస్తోంది…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"స్క్రీన్‌షాట్ సేవ్ చేయబడింది"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"స్క్రీన్‌షాట్‌ని సేవ్ చేయడం సాధ్యం కాలేదు"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"స్క్రీన్‌షాట్ సేవ్ అవ్వకముందే పరికరం అన్‌లాక్ చేయబడాలి"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"స్క్రీన్‌షాట్ తీయడానికి మళ్లీ ట్రై చేయండి"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"స్క్రీన్‌షాట్‌ను సేవ్ చేయడం సాధ్యపడలేదు"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"ప్రసారం చేస్తోంది"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"పేరులేని పరికరం"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"పరికరాలు ఏవీ అందుబాటులో లేవు"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi‑Fi కనెక్ట్ కాలేదు"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • వేగంగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • నెమ్మదిగా ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తి ఛార్జ్"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • ఛార్జ్ అవుతోంది • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>లో పూర్తిగా ఛార్జ్ అవుతుంది"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"కమ్యూనల్ ట్యుటోరియల్‌ను ప్రారంభించడానికి బాణం బటన్‌పై క్లిక్ చేయండి"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్‌డౌన్ మెనూ"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్‌లోని అన్ని యాప్‌లు మరియు డేటా తొలగించబడతాయి."</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 8949360f9508..3f5a47e66aaa 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"กำลังบันทึกภาพหน้าจอไปยังโปรไฟล์งาน…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"บันทึกภาพหน้าจอแล้ว"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"บันทึกภาพหน้าจอไม่ได้"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ต้องปลดล็อกอุปกรณ์ก่อนจึงจะบันทึกภาพหน้าจอได้"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ลองบันทึกภาพหน้าจออีกครั้ง"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"บันทึกภาพหน้าจอไม่ได้"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"กำลังส่ง"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"อุปกรณ์ที่ไม่มีชื่อ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"ไม่มีอุปกรณ์ที่สามารถใช้ได้"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"ไม่ได้เชื่อมต่อ Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างเร็ว • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จอย่างช้าๆ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • กำลังชาร์จ • จะเต็มในอีก <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"คลิกปุ่มลูกศรเพื่อเริ่มบทแนะนำส่วนกลาง"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index e5c11df40e99..3fac637e7adf 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Sine-save ang screenshot sa profile sa trabaho…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Na-save ang screenshot"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Hindi ma-save ang screenshot"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Dapat naka-unlock ang device bago ma-save ang screenshot"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Subukang kumuhang muli ng screenshot"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Hindi ma-save ang screenshot"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Nagka-cast"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Walang pangalang device"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Walang available na mga device"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Hindi nakakonekta sa Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabilis na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Mabagal na nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Nagcha-charge • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> na lang para mapuno"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"I-click ang arrow button para simulan ang communal na tutorial"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 3552d9214fb0..ca357b4143d9 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ekran görüntüsü iş profiline kaydediliyor…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Ekran görüntüsü kaydedildi"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ekran görüntüsü kaydedilemedi"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Ekran görüntüsünün kaydedilebilmesi için cihazın kilidi açık olmalıdır"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Tekrar ekran görüntüsü almayı deneyin"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ekran görüntüsü kaydedilemiyor"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Yayınlanıyor"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Adsız cihaz"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Kullanılabilir cihaz yok"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Kablosuz ağ bağlı değil"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Hızlı şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Yavaş şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Şarj oluyor • Dolmasına <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> kaldı"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Ortak eğitimi başlatmak için ok düğmesini tıklayın"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 2e6aea9d59d3..6078e313b86c 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Зберігання знімка екрана в робочому профілі…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Знімок екрана збережено"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Не вдалося зберегти знімок екрана"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Щоб зберегти знімок екрана, розблокуйте пристрій"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Спробуйте зробити знімок екрана ще раз"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Не вдалося зберегти знімок екрана"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Трансляція"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Пристрій без назви"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Немає пристроїв"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi не під’єднано"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Швидке заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Повільне заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Заряджання • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> до повного заряду"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Натисніть кнопку зі стрілкою, щоб відкрити спільний навчальний посібник"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 2e2fc418116d..5cf763ff47cc 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"اسکرین شاٹ دفتری پروفائل میں محفوظ کیا جا رہا ہے…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"اسکرین شاٹ محفوظ ہو گیا"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکا"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"اسکرین شاٹ محفوظ کرنے سے پہلے آلے کو غیر مقفل کرنا ضروری ہے"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"دوبارہ اسکرین شاٹ لینے کی کوشش کریں"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"اسکرین شاٹ کو محفوظ نہیں کیا جا سکتا"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"کاسٹنگ"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"بغیر نام والا آلہ"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"کوئی آلات دستیاب نہیں ہیں"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"‏Wi-Fi سے منسلک نہیں ہے"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • تیزی سے چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • آہستہ چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • چارج ہو رہا ہے • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> میں مکمل"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"کمیونل ٹیوٹوریل شروع کرنے کے لیے تیر کے نشان والے بٹن پر کلک کریں"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index a8654d5604b1..412dd8a882e9 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -20,8 +20,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_label" msgid="4811759950673118541">"Tizim interfeysi"</string>
- <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash funksiyasi yoqilsinmi?"</string>
- <string name="battery_low_description" msgid="3282977755476423966">"<xliff:g id="PERCENTAGE">%s</xliff:g> batareya quvvati qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
+ <string name="battery_low_title" msgid="5319680173344341779">"Quvvat tejash yoqilsinmi?"</string>
+ <string name="battery_low_description" msgid="3282977755476423966">"Batareya quvvati <xliff:g id="PERCENTAGE">%s</xliff:g> qoldi. Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
<string name="battery_low_intro" msgid="5148725009653088790">"Quvvat tejash funksiyasi Tungi mavzuni yoqadi va fondagi faollikni cheklaydi. Buning natijasida bildirishnomalar kechikishi mumkin."</string>
<string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> qoldi"</string>
<string name="invalid_charger_title" msgid="938685362320735167">"USB orqali quvvatlash imkonsiz"</string>
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Skrinshot ish profiliga saqlanmoqda…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Skrinshot saqlandi"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Skrinshot saqlanmadi"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Skrinshotni saqlashdan oldin qurilma qulflanmagan boʻlishi lozim"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Qayta skrinshot olib ko‘ring"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Skrinshot saqlanmadi"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Translatsiya qilinmoqda"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Nomsiz qurilma"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Qurilmalar topilmadi"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Wi-Fi tarmoqqa ulanmagan"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Yorqinlik"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ranglarni akslantirish"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ranglarni tuzatish"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Tez quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Sekin quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Quvvat olmoqda • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g> qoldi"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Qoʻllanma bilan tanishish uchun strelka tugmasini bosing"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 54437456188f..45dfc451a6bf 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Đang lưu ảnh chụp màn hình vào hồ sơ công việc…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Đã lưu ảnh chụp màn hình"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Không thể lưu ảnh chụp màn hình"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Bạn phải mở khóa thiết bị để chúng tôi có thể lưu ảnh chụp màn hình"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Hãy thử chụp lại màn hình"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Không thể lưu ảnh chụp màn hình"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Đang truyền"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Thiết bị không có tên"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Không có thiết bị nào"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"Chưa kết nối với Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc nhanh • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc chậm • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Đang sạc • Sẽ đầy sau <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Nhấp vào nút mũi tên để bắt đầu xem hướng dẫn chung"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 44c163b03bcb..17a667872f49 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在将屏幕截图保存到工作资料…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"已保存屏幕截图"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"无法保存屏幕截图"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必须先解锁设备,然后才能保存屏幕截图"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"请再次尝试截屏"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"无法保存屏幕截图"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"正在投放"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名设备"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"没有可用设备"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未连接到 WLAN 网络"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在快速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在慢速充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 正在充电 • 将于 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>后充满"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"点击箭头按钮,即可启动公共教程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index f45b2fe1fc39..48fbf7cfc215 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存至工作設定檔…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕擷取畫面已儲存"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕擷取畫面"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再嘗試拍攝螢幕擷取畫面"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"正在放送"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充滿電"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"按一下箭咀鍵,即可開始共用教學課程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 5a5bb9d19c3f..55d4cb614455 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"正在將螢幕截圖儲存到工作資料夾…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"螢幕截圖已儲存"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"無法儲存螢幕截圖"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"必須先解鎖裝置,才能儲存螢幕截圖"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"請再次嘗試拍攝螢幕截圖"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"無法儲存螢幕截圖"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"投放"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"未命名的裝置"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"沒有可用裝置"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"未連線至 Wi-Fi"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 快速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 慢速充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • 充電中 • 將於 <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>後充飽"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"點選箭頭按鈕,即可開始通用教學課程"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會遭到刪除。"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 8b6bfae60199..b883b9a73200 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -76,6 +76,8 @@
<string name="screenshot_saving_work_profile_title" msgid="5332829607308450880">"Ilondoloza isithombe-skrini kuphrofayela yomsebenzi…"</string>
<string name="screenshot_saved_title" msgid="8893267638659083153">"Isithombe-skrini silondoloziwe"</string>
<string name="screenshot_failed_title" msgid="3259148215671936891">"Ayikwazanga ukulondoloza isithombe-skrini"</string>
+ <!-- no translation found for screenshot_failed_external_display_indication (6555673132061101936) -->
+ <skip />
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"Idivayisi kufanele ivulwe ngaphambi kokuthi isithombe-skrini singalondolozwa"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"Zama ukuthatha isithombe-skrini futhi"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"Ayikwazi ukulondoloza isithombe-skrini"</string>
@@ -280,7 +282,8 @@
<string name="quick_settings_casting" msgid="1435880708719268055">"Ukusakaza"</string>
<string name="quick_settings_cast_device_default_name" msgid="6988469571141331700">"Idivayisi engenalo igama"</string>
<string name="quick_settings_cast_detail_empty_text" msgid="2846282280014617785">"Ayikho idivayisi etholakalayo"</string>
- <string name="quick_settings_cast_no_wifi" msgid="6980194769795014875">"I-Wi-Fi ayixhunyiwe"</string>
+ <!-- no translation found for quick_settings_cast_no_network (3863016850468559522) -->
+ <skip />
<string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string>
<string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string>
<string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string>
@@ -395,6 +398,7 @@
<string name="keyguard_indication_charging_time_fast" msgid="8390311020603859480">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja ngokushesha • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_slowly" msgid="301936949731705417">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Ishaja kancane • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
<string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • Iyashaja • Izogcwala ngo-<xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
+ <string name="communal_tutorial_indicator_text" msgid="700342473477865107">"Chofoza inkinobho yomcibisholo ukuze uqalise isifundo somphakathi"</string>
<string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string>
<string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string>
<string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9ac1e9f58dbc..572f6ff2745e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -531,19 +531,25 @@
</string>
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
- config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
- cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
- SystemUI will draw this "protection path" instead of the display cutout path that is normally
- used for anti-aliasing.
+ config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a outer
+ display cutout. If present as well as config_enableDisplayCutoutProtection is set to true,
+ then SystemUI will draw this "protection path" instead of the display cutout path that is
+ normally used for anti-aliasing.
This path will only be drawn when the front-facing camera turns on, otherwise the main
DisplayCutout path will be rendered
-->
<string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
- <!-- ID for the camera that needs extra protection -->
+ <!-- ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedCameraId"></string>
+ <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
+ <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
+
+ <!-- ID for the camera of inner display that needs extra protection -->
+ <string translatable="false" name="config_protectedInnerCameraId"></string>
+
<!-- Comma-separated list of packages to exclude from camera protection e.g.
"com.android.systemui,com.android.xyz" -->
<string translatable="false" name="config_cameraProtectionExcludedPackages"></string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 7a6d29ab3c1f..9e2ebf60fe8f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3224,6 +3224,9 @@
<!--- Label of the dismiss button of the dialog appearing when an external display is connected [CHAR LIMIT=NONE]-->
<string name="dismiss_dialog">Dismiss</string>
+ <!--- Content description of the connected display status bar icon that appears every time a display is connected [CHAR LIMIT=NONE]-->
+ <string name="connected_display_icon_desc">Display connected</string>
+
<!-- Title of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=30] -->
<string name="privacy_dialog_title">Microphone &amp; Camera</string>
<!-- Subtitle of the privacy dialog, shown for active / recent app usage of some phone sensors [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index db46ccf6a827..80f70a0cd2f2 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -33,6 +33,10 @@ data class BiometricModalities(
val hasFingerprint: Boolean
get() = fingerprintProperties != null
+ /** If SFPS authentication is available. */
+ val hasSfps: Boolean
+ get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
+
/** If fingerprint authentication is available (and [faceProperties] is non-null). */
val hasFace: Boolean
get() = faceProperties != null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
index 0aa6b0be5485..9b7cd704aa2f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/dagger/qualifiers/Tracing.kt
@@ -13,10 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.dagger.qualifiers
-package com.android.systemui.qs.tiles.base.interactor
+import javax.inject.Qualifier
-data class QSTileDataRequest(
- val userId: Int,
- val trigger: StateUpdateTrigger,
-)
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Tracing
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 1e896142f718..400f6529eed6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -702,13 +702,18 @@ public class RotationButtonController {
@Override
public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) {
- // Only hide the icon if the top task changes its requestedOrientation
- // Launcher can alter its requestedOrientation while it's not on top, don't hide on this
- Optional.ofNullable(ActivityManagerWrapper.getInstance())
- .map(ActivityManagerWrapper::getRunningTask)
- .ifPresent(a -> {
- if (a.id == taskId) setRotateSuggestionButtonState(false /* visible */);
- });
+ mBgExecutor.execute(() -> {
+ // Only hide the icon if the top task changes its requestedOrientation Launcher can
+ // alter its requestedOrientation while it's not on top, don't hide on this
+ Optional.ofNullable(ActivityManagerWrapper.getInstance())
+ .map(ActivityManagerWrapper::getRunningTask)
+ .ifPresent(a -> {
+ if (a.id == taskId) {
+ mMainThreadHandler.post(() ->
+ setRotateSuggestionButtonState(false /* visible */));
+ }
+ });
+ });
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index eb2066936088..c505bd502985 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,6 +61,8 @@ public final class InteractionJankMonitorWrapper {
InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
public static final int CUJ_OPEN_SEARCH_RESULT =
InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
+ public static final int CUJ_LAUNCHER_UNFOLD_ANIM =
+ InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM;
@IntDef({
CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -77,6 +79,7 @@ public final class InteractionJankMonitorWrapper {
CUJ_CLOSE_ALL_APPS_SWIPE,
CUJ_CLOSE_ALL_APPS_TO_HOME,
CUJ_OPEN_SEARCH_RESULT,
+ CUJ_LAUNCHER_UNFOLD_ANIM,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt
new file mode 100644
index 000000000000..f219cece3ff8
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateRepository.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.unfold.system
+
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/** Provides whether the device is folded. */
+interface DeviceStateRepository {
+ val isFolded: Flow<Boolean>
+}
+
+@Singleton
+class DeviceStateRepositoryImpl
+@Inject
+constructor(
+ private val foldProvider: FoldProvider,
+ @UnfoldMain private val executor: Executor,
+) : DeviceStateRepository {
+
+ override val isFolded: Flow<Boolean>
+ get() =
+ callbackFlow {
+ val callback = FoldCallback { isFolded -> trySend(isFolded) }
+ foldProvider.registerCallback(callback, executor)
+ awaitClose { foldProvider.unregisterCallback(callback) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
index fe607e16661c..7b67e3f3c920 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -48,6 +48,9 @@ abstract class SystemUnfoldSharedModule {
abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
@Binds
+ abstract fun deviceStateRepository(provider: DeviceStateRepositoryImpl): DeviceStateRepository
+
+ @Binds
@UnfoldMain
abstract fun mainExecutor(@Main executor: Executor): Executor
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt
new file mode 100644
index 000000000000..63ea1165ee04
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceStateLogger.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.util
+
+import android.os.Trace
+
+/**
+ * Utility class used to log state changes easily in a track with a custom name.
+ *
+ * Example of usage:
+ * ```kotlin
+ * class MyClass {
+ * val screenStateLogger = TraceStateLogger("Screen state")
+ *
+ * fun onTurnedOn() { screenStateLogger.log("on") }
+ * fun onTurnedOff() { screenStateLogger.log("off") }
+ * }
+ * ```
+ *
+ * This creates a new slice in a perfetto trace only if the state is different than the previous
+ * one.
+ */
+class TraceStateLogger(
+ private val trackName: String,
+ private val logOnlyIfDifferent: Boolean = true,
+ private val instantEvent: Boolean = true
+) {
+
+ private var previousValue: String? = null
+
+ /** If needed, logs the value to a track with name [trackName]. */
+ fun log(newValue: String) {
+ if (instantEvent) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, newValue)
+ }
+ if (logOnlyIfDifferent && previousValue == newValue) return
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, trackName, 0)
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, trackName, newValue, 0)
+ previousValue = newValue
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
index 7b2e1afda700..e45903429728 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/TraceUtils.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,13 +18,25 @@ package com.android.systemui.util
import android.os.Trace
import android.os.TraceNameSupplier
+import android.util.Log
+import com.android.systemui.util.tracing.TraceContextElement
+import com.android.systemui.util.tracing.TraceData
+import com.android.systemui.util.tracing.TraceData.Companion.FIRST_VALID_SPAN
+import com.android.systemui.util.tracing.TraceData.Companion.INVALID_SPAN
+import com.android.systemui.util.tracing.threadLocalTrace
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
+import kotlin.coroutines.coroutineContext
+import kotlin.random.Random
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
/**
* Run a block within a [Trace] section. Calls [Trace.beginSection] before and [Trace.endSection]
@@ -44,6 +56,10 @@ inline fun <T> traceSection(tag: String, block: () -> T): T =
class TraceUtils {
companion object {
+ const val TAG = "TraceUtils"
+ private const val DEBUG_COROUTINE_TRACING = false
+ const val DEFAULT_TRACK_NAME = "AsyncTraces"
+
inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable {
return Runnable { traceSection(tag) { block() } }
}
@@ -73,7 +89,7 @@ class TraceUtils {
* under a single track.
*/
inline fun <T> traceAsync(method: String, block: () -> T): T =
- traceAsync("AsyncTraces", method, block)
+ traceAsync(DEFAULT_TRACK_NAME, method, block)
/**
* Creates an async slice in a track with [trackName] while [block] runs.
@@ -93,16 +109,313 @@ class TraceUtils {
}
/**
- * Convenience method to avoid one indentation level when we want to add a trace when
- * launching a coroutine
+ * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun CoroutineScope.launch(
+ crossinline spanName: () -> String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> Unit
+ ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.launch] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun CoroutineScope.launch(
+ spanName: String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> Unit
+ ): Job = launch(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+ * tracing
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> CoroutineScope.async(
+ crossinline spanName: () -> String,
+ context: CoroutineContext = EmptyCoroutineContext,
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> T
+ ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [CoroutineScope.async] with [traceCoroutine] enable
+ * tracing.
+ *
+ * @see traceCoroutine
*/
- fun <T> CoroutineScope.tracedAsync(
- method: String,
+ inline fun <T> CoroutineScope.async(
+ spanName: String,
context: CoroutineContext = EmptyCoroutineContext,
- start: CoroutineStart = CoroutineStart.DEFAULT,
- block: suspend () -> T
- ): Deferred<T> {
- return async(context, start) { traceAsync(method) { block() } }
+ // TODO(b/306457056): DO NOT pass CoroutineStart; doing so will regress .odex size
+ crossinline block: suspend CoroutineScope.() -> T
+ ): Deferred<T> = async(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> runBlocking(
+ crossinline spanName: () -> String,
+ context: CoroutineContext,
+ crossinline block: suspend () -> T
+ ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [runBlocking] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ inline fun <T> runBlocking(
+ spanName: String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = runBlocking(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ suspend inline fun <T> withContext(
+ spanName: String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * Convenience function for calling [withContext] with [traceCoroutine] to enable tracing.
+ *
+ * @see traceCoroutine
+ */
+ suspend inline fun <T> withContext(
+ crossinline spanName: () -> String,
+ context: CoroutineContext,
+ crossinline block: suspend CoroutineScope.() -> T
+ ): T = withContext(context) { traceCoroutine(spanName) { block() } }
+
+ /**
+ * A hacky way to propagate the value of the COROUTINE_TRACING flag for static usage in this
+ * file. It should only every be set to true during startup. Once true, it cannot be set to
+ * false again.
+ */
+ var coroutineTracingIsEnabled = false
+ set(v) {
+ if (v) field = true
+ }
+
+ /**
+ * Traces a section of work of a `suspend` [block]. The trace sections will appear on the
+ * thread that is currently executing the [block] of work. If the [block] is suspended, all
+ * trace sections added using this API will end until the [block] is resumed, which could
+ * happen either on this thread or on another thread. If a child coroutine is started, it
+ * will inherit the trace sections of its parent. The child will continue to print these
+ * trace sections whether or not the parent coroutine is still running them.
+ *
+ * The current [CoroutineContext] must have a [TraceContextElement] for this API to work.
+ * Otherwise, the trace sections will be dropped.
+ *
+ * For example, in the following trace, Thread #1 ran some work, suspended, then continued
+ * working on Thread #2. Meanwhile, Thread #2 created a new child coroutine which inherited
+ * its trace sections. Then, the original coroutine resumed on Thread #1 before ending.
+ * Meanwhile Thread #3 is still printing trace sections from its parent because they were
+ * copied when it was created. There is no way for the parent to communicate to the child
+ * that it marked these slices as completed. While this might seem counterintuitive, it
+ * allows us to pinpoint the origin of the child coroutine's work.
+ *
+ * ```
+ * Thread #1 | [==== Slice A ====] [==== Slice A ====]
+ * | [==== B ====] [=== B ===]
+ * --------------------------------------------------------------------------------------
+ * Thread #2 | [====== Slice A ======]
+ * | [========= B =========]
+ * | [===== C ======]
+ * --------------------------------------------------------------------------------------
+ * Thread #3 | [== Slice A ==] [== Slice A ==]
+ * | [===== B =====] [===== B =====]
+ * | [===== C =====] [===== C =====]
+ * | [=== D ===]
+ * ```
+ *
+ * @param name The name of the code section to appear in the trace
+ * @see endSlice
+ * @see traceCoroutine
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ suspend inline fun <T> traceCoroutine(
+ spanName: Lazy<String>,
+ crossinline block: suspend () -> T
+ ): T {
+ // For coroutine tracing to work, trace spans must be added and removed even when
+ // tracing is not active (i.e. when TRACE_TAG_APP is disabled). Otherwise, when the
+ // coroutine resumes when tracing is active, we won't know its name.
+ val tracer = getTraceData(spanName)
+ val coroutineSpanCookie = tracer?.beginSpan(spanName.value) ?: INVALID_SPAN
+
+ // For now, also trace to "AsyncTraces". This will allow us to verify the correctness
+ // of the COROUTINE_TRACING feature flag.
+ val asyncTraceCookie =
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP))
+ Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE)
+ else INVALID_SPAN
+ if (asyncTraceCookie != INVALID_SPAN) {
+ Trace.asyncTraceForTrackBegin(
+ Trace.TRACE_TAG_APP,
+ DEFAULT_TRACK_NAME,
+ spanName.value,
+ asyncTraceCookie
+ )
+ }
+ try {
+ return block()
+ } finally {
+ if (asyncTraceCookie != INVALID_SPAN) {
+ Trace.asyncTraceForTrackEnd(
+ Trace.TRACE_TAG_APP,
+ DEFAULT_TRACK_NAME,
+ asyncTraceCookie
+ )
+ }
+ tracer?.endSpan(coroutineSpanCookie)
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ suspend fun getTraceData(spanName: Lazy<String>): TraceData? {
+ if (!coroutineTracingIsEnabled) {
+ logVerbose("Experimental flag COROUTINE_TRACING is off", spanName)
+ } else if (coroutineContext[TraceContextElement] == null) {
+ logVerbose("Current CoroutineContext is missing TraceContextElement", spanName)
+ } else {
+ return threadLocalTrace.get().also {
+ if (it == null) logVerbose("ThreadLocal TraceData is null", spanName)
+ }
+ }
+ return null
+ }
+
+ private fun logVerbose(logMessage: String, spanName: Lazy<String>) {
+ if (DEBUG_COROUTINE_TRACING && Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "$logMessage. Dropping trace section: \"${spanName.value}\"")
+ }
+ }
+
+ /** @see traceCoroutine */
+ suspend inline fun <T> traceCoroutine(
+ spanName: String,
+ crossinline block: suspend () -> T
+ ): T = traceCoroutine(lazyOf(spanName)) { block() }
+
+ /** @see traceCoroutine */
+ suspend inline fun <T> traceCoroutine(
+ crossinline spanName: () -> String,
+ crossinline block: suspend () -> T
+ ): T = traceCoroutine(lazy(LazyThreadSafetyMode.PUBLICATION) { spanName() }) { block() }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has begun running __on
+ * the current thread__. This must be followed by a corresponding call to [endSlice] in a
+ * reasonably short amount of time __on the same thread__ (i.e. _before_ the thread becomes
+ * idle again and starts running other, unrelated work).
+ *
+ * Calls to [beginSlice] and [endSlice] may be nested, and they will render in Perfetto as
+ * follows:
+ * ```
+ * Thread #1 | [==========================]
+ * | [==============]
+ * | [====]
+ * ```
+ *
+ * This function is provided for convenience to wrap a call to [Trace.traceBegin], which is
+ * more verbose to call than [Trace.beginSection], but has the added benefit of not throwing
+ * an [IllegalArgumentException] if the provided string is longer than 127 characters. We
+ * use the term "slice" instead of "section" to be consistent with Perfetto.
+ *
+ * # Avoiding malformed traces
+ *
+ * Improper usage of this API will lead to malformed traces with long slices that sometimes
+ * never end. This will look like the following:
+ * ```
+ * Thread #1 | [===================================================================== ...
+ * | [==============] [====================================== ...
+ * | [=======] [======] [===================== ...
+ * | [=======]
+ * ```
+ *
+ * To avoid this, [beginSlice] and [endSlice] should never be called from `suspend` blocks
+ * (instead, use [traceCoroutine] for tracing suspending functions). While it would be
+ * technically okay to call from a suspending function if that function were to only wrap
+ * non-suspending blocks with [beginSlice] and [endSlice], doing so is risky because suspend
+ * calls could be mistakenly added to that block as the code is refactored.
+ *
+ * Additionally, it is _not_ okay to call [beginSlice] when registering a callback and match
+ * it with a call to [endSlice] inside that callback, even if the callback runs on the same
+ * thread. Doing so would cause malformed traces because the [beginSlice] wasn't closed
+ * before the thread became idle and started running unrelated work.
+ *
+ * @param sliceName The name of the code section to appear in the trace
+ * @see endSlice
+ * @see traceCoroutine
+ */
+ fun beginSlice(sliceName: String) {
+ Trace.traceBegin(Trace.TRACE_TAG_APP, sliceName)
+ }
+
+ /**
+ * Writes a trace message to indicate that a given section of code has ended. This call must
+ * be preceded by a corresponding call to [beginSlice]. See [beginSlice] for important
+ * information regarding usage.
+ *
+ * @see beginSlice
+ * @see traceCoroutine
+ */
+ fun endSlice() {
+ Trace.traceEnd(Trace.TRACE_TAG_APP)
+ }
+
+ /**
+ * Writes a trace message indicating that an instant event occurred on the current thread.
+ * Unlike slices, instant events have no duration and do not need to be matched with another
+ * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+ * occurred:
+ * ```
+ * Thread #1 | [==============] [======]
+ * | [====] ^
+ * | ^
+ * ```
+ *
+ * @param eventName The name of the event to appear in the trace.
+ */
+ fun instant(eventName: String) {
+ Trace.instant(Trace.TRACE_TAG_APP, eventName)
+ }
+
+ /**
+ * Writes a trace message indicating that an instant event occurred on the given track.
+ * Unlike slices, instant events have no duration and do not need to be matched with another
+ * call. Perfetto will display instant events using an arrow pointing to the timestamp they
+ * occurred:
+ * ```
+ * Async | [==============] [======]
+ * Track | [====] ^
+ * Name | ^
+ * ```
+ *
+ * @param trackName The track where the event should appear in the trace.
+ * @param eventName The name of the event to appear in the trace.
+ */
+ fun instantForTrack(trackName: String, eventName: String) {
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, trackName, eventName)
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
new file mode 100644
index 000000000000..4d8c5450d880
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceContextElement.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.instant
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CopyableThreadContextElement
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/**
+ * Used for safely persisting [TraceData] state when coroutines are suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+@OptIn(DelicateCoroutinesApi::class)
+@ExperimentalCoroutinesApi
+class TraceContextElement(private val traceData: TraceData = TraceData()) :
+ CopyableThreadContextElement<TraceData?> {
+
+ companion object Key : CoroutineContext.Key<TraceContextElement>
+
+ override val key: CoroutineContext.Key<TraceContextElement> = Key
+
+ @OptIn(ExperimentalStdlibApi::class)
+ override fun updateThreadContext(context: CoroutineContext): TraceData? {
+ val oldState = threadLocalTrace.get()
+ oldState?.endAllOnThread()
+ threadLocalTrace.set(traceData)
+ instant("resuming ${context[CoroutineDispatcher]}")
+ traceData.beginAllOnThread()
+ return oldState
+ }
+
+ @OptIn(ExperimentalStdlibApi::class)
+ override fun restoreThreadContext(context: CoroutineContext, oldState: TraceData?) {
+ instant("suspending ${context[CoroutineDispatcher]}")
+ traceData.endAllOnThread()
+ threadLocalTrace.set(oldState)
+ oldState?.beginAllOnThread()
+ }
+
+ override fun copyForChild(): CopyableThreadContextElement<TraceData?> {
+ return TraceContextElement(traceData.copy())
+ }
+
+ override fun mergeForChild(overwritingElement: CoroutineContext.Element): CoroutineContext {
+ return TraceContextElement(traceData.copy())
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt
new file mode 100644
index 000000000000..0ae58fc2c45b
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceData.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tracing
+
+import android.os.Build
+import android.util.Log
+import com.android.systemui.util.TraceUtils.Companion.beginSlice
+import com.android.systemui.util.TraceUtils.Companion.endSlice
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+import kotlin.random.Random
+
+/**
+ * Used for giving each thread a unique [TraceData] for thread-local storage. `null` by default.
+ * [threadLocalTrace] can only be used when it is paired with a [TraceContextElement].
+ *
+ * This ThreadLocal will be `null` if either 1) we aren't in a coroutine, or 2) the coroutine we are
+ * in does not have a [TraceContextElement].
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+val threadLocalTrace = ThreadLocal<TraceData?>()
+
+/**
+ * Used for storing trace sections so that they can be added and removed from the currently running
+ * thread when the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @see traceCoroutine
+ */
+class TraceData {
+ private var slices = mutableListOf<TraceSection>()
+
+ /** Adds current trace slices back to the current thread. Called when coroutine is resumed. */
+ fun beginAllOnThread() {
+ slices.forEach { beginSlice(it.name) }
+ }
+
+ /**
+ * Removes all current trace slices from the current thread. Called when coroutine is suspended.
+ */
+ fun endAllOnThread() {
+ for (i in 0..slices.size) {
+ endSlice()
+ }
+ }
+
+ /**
+ * Creates a new trace section with a unique ID and adds it to the current trace data. The slice
+ * will also be added to the current thread immediately. This slice will not propagate to parent
+ * coroutines, or to child coroutines that have already started. The unique ID is used to verify
+ * that the [endSpan] is corresponds to a [beginSpan].
+ */
+ fun beginSpan(name: String): Int {
+ val newSlice = TraceSection(name, Random.nextInt(FIRST_VALID_SPAN, Int.MAX_VALUE))
+ slices.add(newSlice)
+ beginSlice(name)
+ return newSlice.id
+ }
+
+ /**
+ * Used by [TraceContextElement] when launching a child coroutine so that the child coroutine's
+ * state is isolated from the parent.
+ */
+ fun copy(): TraceData {
+ return TraceData().also { it.slices.addAll(slices) }
+ }
+
+ /**
+ * Ends the trace section and validates it corresponds with an earlier call to [beginSpan]. The
+ * trace slice will immediately be removed from the current thread. This information will not
+ * propagate to parent coroutines, or to child coroutines that have already started.
+ */
+ fun endSpan(id: Int) {
+ val v = slices.removeLast()
+ if (v.id != id) {
+ if (STRICT_MODE) {
+ throw IllegalArgumentException(errorMsg)
+ } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, errorMsg)
+ }
+ }
+ endSlice()
+ }
+
+ companion object {
+ private const val TAG = "TraceData"
+ const val INVALID_SPAN = -1
+ const val FIRST_VALID_SPAN = 1
+
+ /**
+ * If true, throw an exception instead of printing a warning when trace sections beginnings
+ * and ends are mismatched.
+ */
+ private val STRICT_MODE = Build.IS_ENG
+
+ private const val errorMsg =
+ "Mismatched trace section. This likely means you are accessing the trace local " +
+ "storage (threadLocalTrace) without a corresponding CopyableThreadContextElement." +
+ " This could happen if you are using a global dispatcher like Dispatchers.IO." +
+ " To fix this, use one of the coroutine contexts provided by the dagger scope " +
+ "(e.g. \"@Main CoroutineContext\")."
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt
new file mode 100644
index 000000000000..b70c4977614a
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/util/tracing/TraceSection.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.tracing
+
+import com.android.systemui.util.TraceUtils.Companion.traceCoroutine
+
+/**
+ * Represents a section of code executing in a coroutine. This can be split up into multiple slices
+ * on different threads as the coroutine is suspended and resumed.
+ *
+ * This is internal machinery for [traceCoroutine]. It cannot be made `internal` or `private`
+ * because [traceCoroutine] is a Public-API inline function.
+ *
+ * @param name the name of the slice to appear on the current thread's track.
+ * @param id used for matching the beginning and end of trace sections and validating correctness
+ * @see traceCoroutine
+ */
+data class TraceSection(
+ val name: String,
+ val id: Int,
+)
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 fae9fec0c26d..a263361c0ab2 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -39,6 +39,12 @@ abstract class FlagsModule {
@IntoSet
abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
+ @Binds
+ @IntoSet
+ abstract fun bindsNotOccludedCondition(
+ impl: NotOccludedCondition
+ ): ConditionalRestarter.Condition
+
@Module
companion object {
@JvmStatic
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 7aacb4efba8e..9684d5e38fa7 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -39,6 +39,12 @@ abstract class FlagsModule {
@IntoSet
abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
+ @Binds
+ @IntoSet
+ abstract fun bindsNotOccludedCondition(
+ impl: NotOccludedCondition
+ ): ConditionalRestarter.Condition
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 13f9d3e1038e..05fb5fa75e9e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -246,7 +246,7 @@ public class KeyguardSimPukViewController
private boolean checkPuk() {
// make sure the puk is at least 8 digits long.
- if (mPasswordEntry.getText().length() == 8) {
+ if (mPasswordEntry.getText().length() >= 8) {
mPukText = mPasswordEntry.getText();
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 2d2ebe99d651..d33d279023da 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,6 +17,7 @@
package com.android.systemui
import android.content.Context
+import android.content.res.Resources
import android.graphics.Path
import android.graphics.Rect
import android.graphics.RectF
@@ -33,37 +34,32 @@ import kotlin.math.roundToInt
*/
class CameraAvailabilityListener(
private val cameraManager: CameraManager,
- private val cutoutProtectionPath: Path,
- private val targetCameraId: String,
+ private val cameraProtectionInfoList: List<CameraProtectionInfo>,
excludedPackages: String,
private val executor: Executor
) {
- private var cutoutBounds = Rect()
private val excludedPackageIds: Set<String>
private val listeners = mutableListOf<CameraTransitionCallback>()
private val availabilityCallback: CameraManager.AvailabilityCallback =
object : CameraManager.AvailabilityCallback() {
override fun onCameraClosed(cameraId: String) {
- if (targetCameraId == cameraId) {
- notifyCameraInactive()
+ cameraProtectionInfoList.forEach {
+ if (cameraId == it.cameraId) {
+ notifyCameraInactive()
+ }
}
}
override fun onCameraOpened(cameraId: String, packageId: String) {
- if (targetCameraId == cameraId && !isExcluded(packageId)) {
- notifyCameraActive()
+ cameraProtectionInfoList.forEach {
+ if (cameraId == it.cameraId && !isExcluded(packageId)) {
+ notifyCameraActive(it)
+ }
}
}
}
init {
- val computed = RectF()
- cutoutProtectionPath.computeBounds(computed, false /* unused */)
- cutoutBounds.set(
- computed.left.roundToInt(),
- computed.top.roundToInt(),
- computed.right.roundToInt(),
- computed.bottom.roundToInt())
excludedPackageIds = excludedPackages.split(",").toSet()
}
@@ -100,8 +96,10 @@ class CameraAvailabilityListener(
cameraManager.unregisterAvailabilityCallback(availabilityCallback)
}
- private fun notifyCameraActive() {
- listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
+ private fun notifyCameraActive(info: CameraProtectionInfo) {
+ listeners.forEach {
+ it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+ }
}
private fun notifyCameraInactive() {
@@ -121,12 +119,11 @@ class CameraAvailabilityListener(
val manager = context
.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val res = context.resources
- val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection)
- val cameraId = res.getString(R.string.config_protectedCameraId)
+ val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
return CameraAvailabilityListener(
- manager, pathFromString(pathString), cameraId, excluded, executor)
+ manager, cameraProtectionInfoList, excluded, executor)
}
private fun pathFromString(pathString: String): Path {
@@ -140,5 +137,53 @@ class CameraAvailabilityListener(
return p
}
+
+ private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
+ val list = mutableListOf<CameraProtectionInfo>()
+ val front = loadCameraProtectionInfo(
+ res,
+ R.string.config_protectedCameraId,
+ R.string.config_frontBuiltInDisplayCutoutProtection
+ )
+ if (front != null) {
+ list.add(front)
+ }
+ val inner = loadCameraProtectionInfo(
+ res,
+ R.string.config_protectedInnerCameraId,
+ R.string.config_innerBuiltInDisplayCutoutProtection
+ )
+ if (inner != null) {
+ list.add(inner)
+ }
+ return list
+ }
+
+ private fun loadCameraProtectionInfo(
+ res: Resources,
+ cameraIdRes: Int,
+ pathRes: Int
+ ): CameraProtectionInfo? {
+ val cameraId = res.getString(cameraIdRes)
+ if (cameraId == null || cameraId.isEmpty()) {
+ return null
+ }
+ val protectionPath = pathFromString(res.getString(pathRes))
+ val computed = RectF()
+ protectionPath.computeBounds(computed)
+ val protectionBounds = Rect(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt()
+ )
+ return CameraProtectionInfo(cameraId, protectionPath, protectionBounds)
+ }
}
+
+ data class CameraProtectionInfo (
+ val cameraId: String,
+ val cutoutProtectionPath: Path,
+ val cutoutBounds: Rect
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
index fd84543ee50b..494efb7d3f87 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
@@ -25,21 +25,24 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.os.UserHandle;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import javax.inject.Inject;
-
+import dagger.Lazy;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import javax.inject.Inject;
+
/**
* Manages handling of guest session persistent notification
* and actions to reset guest or exit guest session
@@ -70,14 +73,14 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
public AlertDialog mResetSessionDialog;
private final UserTracker mUserTracker;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final ResetSessionDialog.Factory mResetSessionDialogFactory;
- private final ExitSessionDialog.Factory mExitSessionDialogFactory;
+ private final ResetSessionDialogFactory mResetSessionDialogFactory;
+ private final ExitSessionDialogFactory mExitSessionDialogFactory;
@Inject
public GuestResetOrExitSessionReceiver(UserTracker userTracker,
BroadcastDispatcher broadcastDispatcher,
- ResetSessionDialog.Factory resetSessionDialogFactory,
- ExitSessionDialog.Factory exitSessionDialogFactory) {
+ ResetSessionDialogFactory resetSessionDialogFactory,
+ ExitSessionDialogFactory exitSessionDialogFactory) {
mUserTracker = userTracker;
mBroadcastDispatcher = broadcastDispatcher;
mResetSessionDialogFactory = resetSessionDialogFactory;
@@ -111,8 +114,8 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id);
mResetSessionDialog.show();
} else if (ACTION_GUEST_EXIT.equals(action)) {
- mExitSessionDialog = mExitSessionDialogFactory.create(currentUser.id,
- currentUser.isEphemeral());
+ mExitSessionDialog = mExitSessionDialogFactory.create(
+ currentUser.isEphemeral(), currentUser.id);
mExitSessionDialog.show();
}
}
@@ -132,43 +135,69 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
}
/**
+ * Factory class to create guest reset dialog instance
+ *
* Dialog shown when asking for confirmation before
* reset and restart of guest user.
*/
- public static final class ResetSessionDialog extends SystemUIDialog implements
- DialogInterface.OnClickListener {
+ public static final class ResetSessionDialogFactory {
+ private final Lazy<SystemUIDialog> mDialogLazy;
+ private final Resources mResources;
+ private final ResetSessionDialogClickListener.Factory mClickListenerFactory;
+
+ @Inject
+ public ResetSessionDialogFactory(
+ Lazy<SystemUIDialog> dialogLazy,
+ @Main Resources resources,
+ ResetSessionDialogClickListener.Factory clickListenerFactory) {
+ mDialogLazy = dialogLazy;
+ mResources = resources;
+ mClickListenerFactory = clickListenerFactory;
+ }
+
+ /** Create a guest reset dialog instance */
+ public AlertDialog create(int userId) {
+ SystemUIDialog dialog = mDialogLazy.get();
+ ResetSessionDialogClickListener listener = mClickListenerFactory.create(
+ userId, dialog);
+ dialog.setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
+ dialog.setMessage(mResources.getString(
+ com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
+ dialog.setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ mResources.getString(android.R.string.cancel),
+ listener);
+ dialog.setButton(DialogInterface.BUTTON_POSITIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_reset_guest_confirm_button),
+ listener);
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
+ }
+ }
+ public static class ResetSessionDialogClickListener implements DialogInterface.OnClickListener {
private final UserSwitcherController mUserSwitcherController;
private final UiEventLogger mUiEventLogger;
private final int mUserId;
+ private final DialogInterface mDialog;
- /** Factory class to create guest reset dialog instance */
@AssistedFactory
public interface Factory {
- /** Create a guest reset dialog instance */
- ResetSessionDialog create(int userId);
+ ResetSessionDialogClickListener create(int userId, DialogInterface dialog);
}
@AssistedInject
- ResetSessionDialog(Context context,
+ public ResetSessionDialogClickListener(
UserSwitcherController userSwitcherController,
UiEventLogger uiEventLogger,
- @Assisted int userId) {
- super(context);
-
- setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
- setMessage(context.getString(
- com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_reset_guest_confirm_button), this);
- setCanceledOnTouchOutside(false);
-
+ @Assisted int userId,
+ @Assisted DialogInterface dialog
+ ) {
mUserSwitcherController = userSwitcherController;
mUiEventLogger = uiEventLogger;
mUserId = userId;
+ mDialog = dialog;
}
@Override
@@ -177,7 +206,7 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL);
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
- cancel();
+ mDialog.cancel();
}
}
}
@@ -186,58 +215,93 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
* Dialog shown when asking for confirmation before
* exit of guest user.
*/
- public static final class ExitSessionDialog extends SystemUIDialog implements
- DialogInterface.OnClickListener {
+ public static final class ExitSessionDialogFactory {
+ private final Lazy<SystemUIDialog> mDialogLazy;
+ private final ExitSessionDialogClickListener.Factory mClickListenerFactory;
+ private final Resources mResources;
+
+ @Inject
+ public ExitSessionDialogFactory(
+ Lazy<SystemUIDialog> dialogLazy,
+ ExitSessionDialogClickListener.Factory clickListenerFactory,
+ @Main Resources resources) {
+ mDialogLazy = dialogLazy;
+ mClickListenerFactory = clickListenerFactory;
+ mResources = resources;
+ }
+ public AlertDialog create(boolean isEphemeral, int userId) {
+ SystemUIDialog dialog = mDialogLazy.get();
+ ExitSessionDialogClickListener clickListener = mClickListenerFactory.create(
+ isEphemeral, userId, dialog);
+ if (isEphemeral) {
+ dialog.setTitle(mResources.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_title));
+ dialog.setMessage(mResources.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_message));
+ dialog.setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ mResources.getString(android.R.string.cancel),
+ clickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_POSITIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_button),
+ clickListener);
+ } else {
+ dialog.setTitle(mResources.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_title_non_ephemeral));
+ dialog.setMessage(mResources.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_message_non_ephemeral));
+ dialog.setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ mResources.getString(android.R.string.cancel),
+ clickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_NEGATIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_exit_clear_data_button),
+ clickListener);
+ dialog.setButton(
+ DialogInterface.BUTTON_POSITIVE,
+ mResources.getString(
+ com.android.settingslib.R.string.guest_exit_save_data_button),
+ clickListener);
+ }
+ dialog.setCanceledOnTouchOutside(false);
+
+ return dialog;
+ }
+
+ }
+
+ public static class ExitSessionDialogClickListener implements DialogInterface.OnClickListener {
private final UserSwitcherController mUserSwitcherController;
+ private final boolean mIsEphemeral;
private final int mUserId;
- private boolean mIsEphemeral;
+ private final DialogInterface mDialog;
- /** Factory class to create guest exit dialog instance */
@AssistedFactory
public interface Factory {
- /** Create a guest exit dialog instance */
- ExitSessionDialog create(int userId, boolean isEphemeral);
+ ExitSessionDialogClickListener create(
+ boolean isEphemeral,
+ int userId,
+ DialogInterface dialog);
}
@AssistedInject
- ExitSessionDialog(Context context,
+ public ExitSessionDialogClickListener(
UserSwitcherController userSwitcherController,
+ @Assisted boolean isEphemeral,
@Assisted int userId,
- @Assisted boolean isEphemeral) {
- super(context);
-
- if (isEphemeral) {
- setTitle(context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_title));
- setMessage(context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_dialog_button), this);
- } else {
- setTitle(context.getString(
- com.android.settingslib
- .R.string.guest_exit_dialog_title_non_ephemeral));
- setMessage(context.getString(
- com.android.settingslib
- .R.string.guest_exit_dialog_message_non_ephemeral));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_NEGATIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_clear_data_button), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(
- com.android.settingslib.R.string.guest_exit_save_data_button), this);
- }
- setCanceledOnTouchOutside(false);
-
+ @Assisted DialogInterface dialog
+ ) {
mUserSwitcherController = userSwitcherController;
- mUserId = userId;
mIsEphemeral = isEphemeral;
+ mUserId = userId;
+ mDialog = dialog;
}
@Override
@@ -249,7 +313,7 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
// Cancel clicked, do nothing
- cancel();
+ mDialog.cancel();
}
} else {
if (which == DialogInterface.BUTTON_POSITIVE) {
@@ -261,7 +325,7 @@ public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true);
} else if (which == DialogInterface.BUTTON_NEUTRAL) {
// Cancel clicked, do nothing
- cancel();
+ mDialog.cancel();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index b573fadea15e..0f5f869cba5d 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -27,6 +27,7 @@ import androidx.annotation.NonNull;
import com.android.systemui.res.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -36,14 +37,14 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.settings.SecureSettings;
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
/**
* Manages notification when a guest session is resumed.
*/
@@ -58,7 +59,7 @@ public class GuestResumeSessionReceiver {
private final Executor mMainExecutor;
private final UserTracker mUserTracker;
private final SecureSettings mSecureSettings;
- private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+ private final ResetSessionDialogFactory mResetSessionDialogFactory;
private final GuestSessionNotification mGuestSessionNotification;
@VisibleForTesting
@@ -104,7 +105,7 @@ public class GuestResumeSessionReceiver {
UserTracker userTracker,
SecureSettings secureSettings,
GuestSessionNotification guestSessionNotification,
- ResetSessionDialog.Factory resetSessionDialogFactory) {
+ ResetSessionDialogFactory resetSessionDialogFactory) {
mMainExecutor = mainExecutor;
mUserTracker = userTracker;
mSecureSettings = secureSettings;
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 453a7a6d3536..8ea867bbf3fc 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -28,6 +28,8 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.TraceUtils.Companion.async
+import com.android.systemui.util.TraceUtils.Companion.withContext
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlin.math.max
@@ -263,7 +265,7 @@ constructor(
* not being throttled.
*/
private suspend fun refreshThrottling(): Long {
- return withContext(backgroundDispatcher) {
+ return withContext("$TAG#refreshThrottling", backgroundDispatcher) {
val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
val deadline = async { repository.getThrottlingEndTimestamp() }
val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
@@ -311,6 +313,10 @@ constructor(
DomainLayerAuthenticationMethodModel.Pattern
}
}
+
+ companion object {
+ const val TAG = "AuthenticationInteractor"
+ }
}
/** Result of a user authentication attempt. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
deleted file mode 100644
index 3f2da5e144c5..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ /dev/null
@@ -1,118 +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.biometrics
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthBiometricFaceIconController"
-
-/** Face only icon animator for BiometricPrompt. */
-class AuthBiometricFaceIconController(
- context: Context,
- iconView: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
- // false = dark to light, true = light to dark
- private var lastPulseLightToDark = false
-
- private var state: BiometricState = BiometricState.STATE_IDLE
-
- init {
- val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
- iconView.layoutParams.width = size
- iconView.layoutParams.height = size
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
- }
-
- private fun startPulsing() {
- lastPulseLightToDark = false
- animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true)
- }
-
- private fun pulseInNextDirection() {
- val iconRes = if (lastPulseLightToDark) {
- R.drawable.face_dialog_pulse_dark_to_light
- } else {
- R.drawable.face_dialog_pulse_light_to_dark
- }
- animateIcon(iconRes, true /* repeat */)
- lastPulseLightToDark = !lastPulseLightToDark
- }
-
- override fun handleAnimationEnd(drawable: Drawable) {
- if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
- pulseInNextDirection()
- }
- }
-
- override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
- val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
- if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) {
- showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
- )
- } else if (newState == BiometricState.STATE_AUTHENTICATING) {
- startPulsing()
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticating
- )
- } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_confirmed
- )
- } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) {
- animateIconOnce(R.drawable.face_dialog_error_to_idle)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
- )
- } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
- animateIconOnce(R.drawable.face_dialog_dark_to_error)
- iconView.contentDescription = context.getString(
- R.string.keyguard_face_failed
- )
- } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) {
- animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) {
- animateIconOnce(R.drawable.face_dialog_wink_from_dark)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_authenticated
- )
- } else if (newState == BiometricState.STATE_IDLE) {
- showStaticDrawable(R.drawable.face_dialog_idle_static)
- iconView.contentDescription = context.getString(
- R.string.biometric_dialog_face_icon_description_idle
- )
- } else {
- Log.w(TAG, "Unhandled state: $newState")
- }
- state = newState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
deleted file mode 100644
index 09eabf2aa430..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ /dev/null
@@ -1,67 +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.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-/** Face/Fingerprint combined icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintAndFaceIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay) {
-
- override val actsAsConfirmButton: Boolean = true
-
- override fun shouldAnimateIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Boolean = when (newState) {
- STATE_PENDING_CONFIRMATION -> true
- else -> super.shouldAnimateIconViewForTransition(oldState, newState)
- }
-
- @RawRes
- override fun getAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Int? = when (newState) {
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_PENDING_CONFIRMATION) {
- R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
- } else {
- super.getAnimationForTransition(oldState, newState)
- }
- }
- STATE_PENDING_CONFIRMATION -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_unlock_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
- }
- }
- else -> super.getAnimationForTransition(oldState, newState)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
deleted file mode 100644
index 0ad3848299f9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ /dev/null
@@ -1,342 +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.biometrics
-
-import android.annotation.RawRes
-import android.content.Context
-import android.content.Context.FINGERPRINT_SERVICE
-import android.hardware.fingerprint.FingerprintManager
-import android.view.DisplayInfo
-import android.view.Surface
-import android.view.View
-import androidx.annotation.VisibleForTesting
-import com.airbnb.lottie.LottieAnimationView
-import com.android.settingslib.widget.LottieColorUtils
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
-
-
-/** Fingerprint only icon animator for BiometricPrompt. */
-open class AuthBiometricFingerprintIconController(
- context: Context,
- iconView: LottieAnimationView,
- protected val iconViewOverlay: LottieAnimationView
-) : AuthIconController(context, iconView) {
-
- private val isSideFps: Boolean
- private val isReverseDefaultRotation =
- context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
-
- var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
- set(value) {
- if (field == value) {
- return
- }
- iconViewOverlay.layoutParams.width = value.first
- iconViewOverlay.layoutParams.height = value.second
- iconView.layoutParams.width = value.first
- iconView.layoutParams.height = value.second
- field = value
- }
-
- init {
- iconLayoutParamSize = Pair(context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_width),
- context.resources.getDimensionPixelSize(
- R.dimen.biometric_dialog_fingerprint_icon_height))
- isSideFps =
- (context.getSystemService(FINGERPRINT_SERVICE) as FingerprintManager?)?.let { fpm ->
- fpm.sensorPropertiesInternal.any { it.isAnySidefpsType }
- } ?: false
- preloadAssets(context)
- val displayInfo = DisplayInfo()
- context.display?.getDisplayInfo(displayInfo)
- if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
- iconView.rotation = 180f
- }
- }
-
- private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
- val displayInfo = DisplayInfo()
- context.display?.getDisplayInfo(displayInfo)
- val rotation = getRotationFromDefault(displayInfo.rotation)
- val iconViewOverlayAnimation =
- getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
-
- if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
- iconViewOverlay.setAnimation(iconViewOverlayAnimation)
- }
-
- val iconContentDescription = getIconContentDescription(newState)
- if (iconContentDescription != null) {
- iconView.contentDescription = iconContentDescription
- }
-
- iconView.frame = 0
- iconViewOverlay.frame = 0
- if (shouldAnimateSfpsIconViewForTransition(lastState, newState)) {
- iconView.playAnimation()
- }
-
- if (shouldAnimateIconViewOverlayForTransition(lastState, newState)) {
- iconViewOverlay.playAnimation()
- }
-
- LottieColorUtils.applyDynamicColors(context, iconView)
- LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
- }
-
- private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
- val icon = getAnimationForTransition(lastState, newState) ?: return
-
- if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
- iconView.setAnimation(icon)
- }
-
- val iconContentDescription = getIconContentDescription(newState)
- if (iconContentDescription != null) {
- iconView.contentDescription = iconContentDescription
- }
-
- iconView.frame = 0
- if (shouldAnimateIconViewForTransition(lastState, newState)) {
- iconView.playAnimation()
- }
- LottieColorUtils.applyDynamicColors(context, iconView)
- }
-
- override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
- if (isSideFps) {
- updateIconSideFps(lastState, newState)
- } else {
- iconViewOverlay.visibility = View.GONE
- updateIconNormal(lastState, newState)
- }
- }
-
- @VisibleForTesting
- fun getIconContentDescription(newState: BiometricState): CharSequence? {
- val id = when (newState) {
- STATE_IDLE,
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING,
- STATE_AUTHENTICATED ->
- if (isSideFps) {
- R.string.security_settings_sfps_enroll_find_sensor_message
- } else {
- R.string.fingerprint_dialog_touch_sensor
- }
- STATE_PENDING_CONFIRMATION ->
- if (isSideFps) {
- R.string.security_settings_sfps_enroll_find_sensor_message
- } else {
- R.string.fingerprint_dialog_authenticated_confirmation
- }
- STATE_ERROR,
- STATE_HELP -> R.string.biometric_dialog_try_again
- else -> null
- }
- return if (id != null) context.getString(id) else null
- }
-
- protected open fun shouldAnimateIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- private fun shouldAnimateSfpsIconViewForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING ->
- oldState == STATE_ERROR || oldState == STATE_HELP || oldState == STATE_IDLE
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- protected open fun shouldAnimateIconViewOverlayForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ) = when (newState) {
- STATE_HELP,
- STATE_ERROR -> true
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP
- STATE_AUTHENTICATED -> true
- else -> false
- }
-
- @RawRes
- protected open fun getAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState
- ): Int? {
- val id = when (newState) {
- STATE_HELP,
- STATE_ERROR -> {
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
- }
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
- }
- }
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- R.raw.fingerprint_dialogue_error_to_success_lottie
- } else {
- R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
- }
- }
- else -> return null
- }
- return if (id != null) return id else null
- }
-
- private fun getRotationFromDefault(rotation: Int): Int =
- if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
-
- @RawRes
- private fun getSideFpsOverlayAnimationForTransition(
- oldState: BiometricState,
- newState: BiometricState,
- rotation: Int
- ): Int? = when (newState) {
- STATE_HELP,
- STATE_ERROR -> {
- when (rotation) {
- Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- else -> R.raw.biometricprompt_fingerprint_to_error_landscape
- }
- }
- STATE_AUTHENTICATING_ANIMATING_IN,
- STATE_AUTHENTICATING -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
- }
- } else {
- when (rotation) {
- Surface.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_fingerprint_to_error_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
- else -> R.raw.biometricprompt_fingerprint_to_error_landscape
- }
- }
- }
- STATE_AUTHENTICATED -> {
- if (oldState == STATE_ERROR || oldState == STATE_HELP) {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_error_to_success_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_error_to_success_landscape
- }
- } else {
- when (rotation) {
- Surface.ROTATION_0 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- Surface.ROTATION_90 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
- Surface.ROTATION_180 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- Surface.ROTATION_270 ->
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
- else -> R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
- }
- }
- }
- else -> null
- }
-
- private fun preloadAssets(context: Context) {
- if (isSideFps) {
- cacheLottieAssetsInContext(
- context,
- R.raw.biometricprompt_fingerprint_to_error_landscape,
- R.raw.biometricprompt_folded_base_bottomright,
- R.raw.biometricprompt_folded_base_default,
- R.raw.biometricprompt_folded_base_topleft,
- R.raw.biometricprompt_landscape_base,
- R.raw.biometricprompt_portrait_base_bottomright,
- R.raw.biometricprompt_portrait_base_topleft,
- R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
- R.raw.biometricprompt_symbol_error_to_success_landscape,
- R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
- R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
- R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
- )
- } else {
- cacheLottieAssetsInContext(
- context,
- R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
- R.raw.fingerprint_dialogue_error_to_success_lottie,
- R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
- R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 054bd082edb7..8d1d90588fc3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.MainThread
@@ -25,7 +41,7 @@ constructor(
shadeExpansionCollectorJob =
scope.launch {
// wait for it to emit true once
- shadeInteractorLazy.get().isAnyExpanding.first { it }
+ shadeInteractorLazy.get().isUserInteracting.first { it }
onShadeInteraction.run()
}
shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
deleted file mode 100644
index 958213afacdf..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ /dev/null
@@ -1,100 +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.biometrics
-
-import android.annotation.DrawableRes
-import android.content.Context
-import android.graphics.drawable.Animatable2
-import android.graphics.drawable.AnimatedVectorDrawable
-import android.graphics.drawable.Drawable
-import android.util.Log
-import com.airbnb.lottie.LottieAnimationView
-import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-
-private const val TAG = "AuthIconController"
-
-/** Controller for animating the BiometricPrompt icon/affordance. */
-abstract class AuthIconController(
- protected val context: Context,
- protected val iconView: LottieAnimationView
-) : Animatable2.AnimationCallback() {
-
- /** If this controller should ignore events and pause. */
- var deactivated: Boolean = false
-
- /** If the icon view should be treated as an alternate "confirm" button. */
- open val actsAsConfirmButton: Boolean = false
-
- final override fun onAnimationStart(drawable: Drawable) {
- super.onAnimationStart(drawable)
- }
-
- final override fun onAnimationEnd(drawable: Drawable) {
- super.onAnimationEnd(drawable)
-
- if (!deactivated) {
- handleAnimationEnd(drawable)
- }
- }
-
- /** Set the icon to a static image. */
- protected fun showStaticDrawable(@DrawableRes iconRes: Int) {
- iconView.setImageDrawable(context.getDrawable(iconRes))
- }
-
- /** Animate a resource. */
- protected fun animateIconOnce(@DrawableRes iconRes: Int) {
- animateIcon(iconRes, false)
- }
-
- /** Animate a resource. */
- protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) {
- if (!deactivated) {
- val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable
- iconView.setImageDrawable(icon)
- icon.forceAnimationOnUI()
- if (repeat) {
- icon.registerAnimationCallback(this)
- }
- icon.start()
- }
- }
-
- /** Update the icon to reflect the [newState]. */
- fun updateState(lastState: BiometricState, newState: BiometricState) {
- if (deactivated) {
- Log.w(TAG, "Ignoring updateState when deactivated: $newState")
- } else {
- updateIcon(lastState, newState)
- }
- }
-
- /** Call during [updateState] if the controller is not [deactivated]. */
- abstract fun updateIcon(lastState: BiometricState, newState: BiometricState)
-
- /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
- open fun handleAnimationEnd(drawable: Drawable) {}
-
- // TODO(b/251476085): Migrate this to an extension at the appropriate level?
- /** Load the given [rawResources] immediately so they are cached for use in the [context]. */
- protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {
- for (res in rawResources) {
- LottieCompositionFactory.fromRawRes(context, res)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index c4c52e8b358e..050b399fd3e8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -42,8 +42,11 @@ import kotlinx.coroutines.flow.stateIn
/** Repository for the current state of the display */
interface DisplayStateRepository {
/**
- * Whether or not the direction rotation is applied to get to an application's requested
- * orientation is reversed.
+ * If true, the direction rotation is applied to get to an application's requested orientation
+ * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+ * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+ * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+ * true here reverses that logic. See go/natural-orientation for context.
*/
val isReverseDefaultRotation: Boolean
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index a317a0684055..427361d4b17a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -57,6 +57,15 @@ interface DisplayStateInteractor {
/** Display change event indicating a change to the given displayId has occurred. */
val displayChanges: Flow<Int>
+ /**
+ * If true, the direction rotation is applied to get to an application's requested orientation
+ * is reversed. Normally, the model is that landscape is clockwise from portrait; thus on a
+ * portrait device an app requesting landscape will cause a clockwise rotation, and on a
+ * landscape device an app requesting portrait will cause a counter-clockwise rotation. Setting
+ * true here reverses that logic. See go/natural-orientation for context.
+ */
+ val isReverseDefaultRotation: Boolean
+
/** Called on configuration changes, used to keep the display state in sync */
fun onConfigurationChanged(newConfig: Configuration)
}
@@ -112,6 +121,8 @@ constructor(
override val currentRotation: StateFlow<DisplayRotation> =
displayStateRepository.currentRotation
+ override val isReverseDefaultRotation: Boolean = displayStateRepository.isReverseDefaultRotation
+
override fun onConfigurationChanged(newConfig: Configuration) {
screenSizeFoldProvider.onConfigurationChange(newConfig)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index cef0be09d3ce..0d72b9c07d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -29,11 +29,10 @@ import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.systemui.res.R;
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.AuthDialog;
import com.android.systemui.biometrics.UdfpsDialogMeasureAdapter;
+import com.android.systemui.res.R;
import kotlin.Pair;
@@ -85,13 +84,13 @@ public class BiometricPromptLayout extends LinearLayout {
}
@Deprecated
- public void updateFingerprintAffordanceSize(
- @NonNull AuthBiometricFingerprintIconController iconController) {
+ public Pair<Integer, Integer> getUpdatedFingerprintAffordanceSize() {
if (mUdfpsAdapter != null) {
final int sensorDiameter = mUdfpsAdapter.getSensorDiameter(
mScaleFactorProvider.provide());
- iconController.setIconLayoutParamSize(new Pair(sensorDiameter, sensorDiameter));
+ return new Pair(sensorDiameter, sensorDiameter);
}
+ return null;
}
@NonNull
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index c29efc0fcab9..ac48b6a2b11e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -38,11 +38,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.AuthBiometricFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
-import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
-import com.android.systemui.biometrics.AuthIconController
+import com.airbnb.lottie.LottieCompositionFactory
import com.android.systemui.biometrics.AuthPanelController
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -56,6 +52,7 @@ import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
@@ -101,10 +98,15 @@ object BiometricViewBinder {
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
descriptionView.movementMethod = ScrollingMovementMethod()
- val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+ val iconOverlayView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
- PromptFingerprintIconViewBinder.bind(iconView, viewModel.fingerprintIconViewModel)
+ PromptIconViewBinder.bind(
+ iconView,
+ iconOverlayView,
+ view.getUpdatedFingerprintAffordanceSize(),
+ viewModel.iconViewModel
+ )
val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
@@ -128,9 +130,21 @@ object BiometricViewBinder {
// bind to prompt
var boundSize = false
+
view.repeatWhenAttached {
// these do not change and need to be set before any size transitions
val modalities = viewModel.modalities.first()
+ if (modalities.hasFingerprint) {
+ /**
+ * Load the given [rawResources] immediately so they are cached for use in the
+ * [context].
+ */
+ val rawResources = viewModel.iconViewModel.getRawAssets(modalities.hasSfps)
+ for (res in rawResources) {
+ LottieCompositionFactory.fromRawRes(view.context, res)
+ }
+ }
+
titleView.text = viewModel.title.first()
descriptionView.text = viewModel.description.first()
subtitleView.text = viewModel.subtitle.first()
@@ -148,27 +162,8 @@ object BiometricViewBinder {
legacyCallback.onButtonTryAgain()
}
- // TODO(b/251476085): migrate legacy icon controllers and remove
- var legacyState = viewModel.legacyState.value
- val iconController =
- modalities.asIconController(
- view.context,
- iconView,
- iconViewOverlay,
- )
- adapter.attach(this, iconController, modalities, legacyCallback)
- if (iconController is AuthBiometricFingerprintIconController) {
- view.updateFingerprintAffordanceSize(iconController)
- }
- if (iconController is HackyCoexIconController) {
- iconController.faceMode = !viewModel.isConfirmationRequired.first()
- }
+ adapter.attach(this, modalities, legacyCallback)
- // the icon controller must be created before this happens for the legacy
- // sizing code in BiometricPromptLayout to work correctly. Simplify this
- // when those are also migrated. (otherwise the icon size may not be set to
- // a pixel value before the view is measured and WRAP_CONTENT will be incorrectly
- // used as part of the measure spec)
if (!boundSize) {
boundSize = true
BiometricViewSizeBinder.bind(
@@ -212,14 +207,6 @@ object BiometricViewBinder {
) {
legacyCallback.onStartDelayedFingerprintSensor()
}
-
- if (newMode.isStarted) {
- // do wonky switch from implicit to explicit flow
- (iconController as? HackyCoexIconController)?.faceMode = false
- viewModel.showAuthenticating(
- modalities.asDefaultHelpMessage(view.context),
- )
- }
}
}
@@ -312,7 +299,7 @@ object BiometricViewBinder {
viewModel.isIconConfirmButton
.map { isPending ->
when {
- isPending && iconController.actsAsConfirmButton ->
+ isPending && modalities.hasFaceAndFingerprint ->
View.OnTouchListener { _: View, event: MotionEvent ->
viewModel.onOverlayTouch(event)
}
@@ -320,22 +307,11 @@ object BiometricViewBinder {
}
}
.collect { onTouch ->
- iconViewOverlay.setOnTouchListener(onTouch)
+ iconOverlayView.setOnTouchListener(onTouch)
iconView.setOnTouchListener(onTouch)
}
}
- // TODO(b/251476085): remove w/ legacy icon controllers
- // set icon affordance using legacy states
- // like the old code, this causes animations to repeat on config changes :(
- // but keep behavior for now as no one has complained...
- launch {
- viewModel.legacyState.collect { newState ->
- iconController.updateState(legacyState, newState)
- legacyState = newState
- }
- }
-
// dismiss prompt when authenticated and confirmed
launch {
viewModel.isAuthenticated.collect { authState ->
@@ -350,7 +326,7 @@ object BiometricViewBinder {
// Allow icon to be used as confirmation button with a11y enabled
if (accessibilityManager.isTouchExplorationEnabled) {
- iconViewOverlay.setOnClickListener {
+ iconOverlayView.setOnClickListener {
viewModel.confirmAuthenticated()
}
iconView.setOnClickListener { viewModel.confirmAuthenticated() }
@@ -377,7 +353,6 @@ object BiometricViewBinder {
launch {
viewModel.message.collect { promptMessage ->
val isError = promptMessage is PromptMessage.Error
-
indicatorMessageView.text = promptMessage.message
indicatorMessageView.setTextColor(
if (isError) textColorError else textColorHint
@@ -472,9 +447,6 @@ class Spaghetti(
private var modalities: BiometricModalities = BiometricModalities()
private var legacyCallback: Callback? = null
- var legacyIconController: AuthIconController? = null
- private set
-
// hacky way to suppress lockout errors
private val lockoutErrorStrings =
listOf(
@@ -485,24 +457,20 @@ class Spaghetti(
fun attach(
lifecycleOwner: LifecycleOwner,
- iconController: AuthIconController,
activeModalities: BiometricModalities,
callback: Callback,
) {
modalities = activeModalities
- legacyIconController = iconController
legacyCallback = callback
lifecycleOwner.lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
lifecycleScope = owner.lifecycleScope
- iconController.deactivated = false
}
override fun onDestroy(owner: LifecycleOwner) {
lifecycleScope = null
- iconController.deactivated = true
}
}
)
@@ -626,61 +594,9 @@ private fun BiometricModalities.asDefaultHelpMessage(context: Context): String =
else -> ""
}
-private fun BiometricModalities.asIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-): AuthIconController =
- when {
- hasFaceAndFingerprint -> HackyCoexIconController(context, iconView, iconViewOverlay)
- hasFingerprint -> AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
- hasFace -> AuthBiometricFaceIconController(context, iconView)
- else -> throw IllegalStateException("unexpected view type :$this")
- }
-
private fun Boolean.asVisibleOrGone(): Int = if (this) View.VISIBLE else View.GONE
private fun Boolean.asVisibleOrHidden(): Int = if (this) View.VISIBLE else View.INVISIBLE
// TODO(b/251476085): proper type?
typealias BiometricJankListener = Animator.AnimatorListener
-
-// TODO(b/251476085): delete - temporary until the legacy icon controllers are replaced
-private class HackyCoexIconController(
- context: Context,
- iconView: LottieAnimationView,
- iconViewOverlay: LottieAnimationView,
-) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
-
- private var state: Spaghetti.BiometricState? = null
- private val faceController = AuthBiometricFaceIconController(context, iconView)
-
- var faceMode: Boolean = true
- set(value) {
- if (field != value) {
- field = value
-
- faceController.deactivated = !value
- iconView.setImageIcon(null)
- iconViewOverlay.setImageIcon(null)
- state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) }
- }
- }
-
- override fun updateIcon(
- lastState: Spaghetti.BiometricState,
- newState: Spaghetti.BiometricState,
- ) {
- if (deactivated) {
- return
- }
-
- if (faceMode) {
- faceController.updateIcon(lastState, newState)
- } else {
- super.updateIcon(lastState, newState)
- }
-
- state = newState
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
deleted file mode 100644
index d28f1dc78c13..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptFingerprintIconViewBinder.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.biometrics.ui.viewmodel.PromptFingerprintIconViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
-
-/** Sub-binder for [BiometricPromptLayout.iconView]. */
-object PromptFingerprintIconViewBinder {
-
- /** Binds [BiometricPromptLayout.iconView] to [PromptFingerprintIconViewModel]. */
- @JvmStatic
- fun bind(view: LottieAnimationView, viewModel: PromptFingerprintIconViewModel) {
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.onConfigurationChanged(view.context.resources.configuration)
- launch {
- viewModel.iconAsset.collect { iconAsset ->
- if (iconAsset != -1) {
- view.setAnimation(iconAsset)
- // TODO: must replace call below once non-sfps asset logic and
- // shouldAnimateIconView logic is migrated to this ViewModel.
- view.playAnimation()
- }
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
new file mode 100644
index 000000000000..475ef18e5099
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.binder
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.settingslib.widget.LottieColorUtils
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel
+import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
+import com.android.systemui.util.kotlin.Utils.Companion.toTriple
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [BiometricPromptLayout.iconView]. */
+object PromptIconViewBinder {
+ /**
+ * Binds [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] to
+ * [PromptIconViewModel].
+ */
+ @JvmStatic
+ fun bind(
+ iconView: LottieAnimationView,
+ iconOverlayView: LottieAnimationView,
+ iconViewLayoutParamSizeOverride: Pair<Int, Int>?,
+ viewModel: PromptIconViewModel
+ ) {
+ iconView.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.onConfigurationChanged(iconView.context.resources.configuration)
+ if (iconViewLayoutParamSizeOverride != null) {
+ iconView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+ iconView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+
+ iconOverlayView.layoutParams.width = iconViewLayoutParamSizeOverride.first
+ iconOverlayView.layoutParams.height = iconViewLayoutParamSizeOverride.second
+ }
+
+ var faceIcon: AnimatedVectorDrawable? = null
+ val faceIconCallback =
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationStart(drawable: Drawable) {
+ viewModel.onAnimationStart()
+ }
+
+ override fun onAnimationEnd(drawable: Drawable) {
+ viewModel.onAnimationEnd()
+ }
+ }
+
+ launch {
+ viewModel.activeAuthType.collect { activeAuthType ->
+ if (iconViewLayoutParamSizeOverride == null) {
+ val width: Int
+ val height: Int
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> {
+ width = viewModel.fingerprintIconWidth
+ height = viewModel.fingerprintIconHeight
+ }
+ AuthType.Face -> {
+ width = viewModel.faceIconWidth
+ height = viewModel.faceIconHeight
+ }
+ }
+
+ iconView.layoutParams.width = width
+ iconView.layoutParams.height = height
+
+ iconOverlayView.layoutParams.width = width
+ iconOverlayView.layoutParams.height = height
+ }
+ }
+ }
+
+ launch {
+ viewModel.iconAsset
+ .sample(
+ combine(
+ viewModel.activeAuthType,
+ viewModel.shouldAnimateIconView,
+ viewModel.shouldRepeatAnimation,
+ viewModel.showingError,
+ ::toQuad
+ ),
+ ::toQuint
+ )
+ .collect {
+ (
+ iconAsset,
+ activeAuthType,
+ shouldAnimateIconView,
+ shouldRepeatAnimation,
+ showingError) ->
+ if (iconAsset != -1) {
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> {
+ iconView.setAnimation(iconAsset)
+ iconView.frame = 0
+
+ if (shouldAnimateIconView) {
+ iconView.playAnimation()
+ }
+ }
+ AuthType.Face -> {
+ faceIcon?.apply {
+ unregisterAnimationCallback(faceIconCallback)
+ stop()
+ }
+ faceIcon =
+ iconView.context.getDrawable(iconAsset)
+ as AnimatedVectorDrawable
+ faceIcon?.apply {
+ iconView.setImageDrawable(this)
+ if (shouldAnimateIconView) {
+ forceAnimationOnUI()
+ if (shouldRepeatAnimation) {
+ registerAnimationCallback(faceIconCallback)
+ }
+ start()
+ }
+ }
+ }
+ }
+ LottieColorUtils.applyDynamicColors(iconView.context, iconView)
+ viewModel.setPreviousIconWasError(showingError)
+ }
+ }
+ }
+
+ launch {
+ viewModel.iconOverlayAsset
+ .sample(
+ combine(
+ viewModel.shouldAnimateIconOverlay,
+ viewModel.showingError,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) ->
+ if (iconOverlayAsset != -1) {
+ iconOverlayView.setAnimation(iconOverlayAsset)
+ iconOverlayView.frame = 0
+ LottieColorUtils.applyDynamicColors(
+ iconOverlayView.context,
+ iconOverlayView
+ )
+
+ if (shouldAnimateIconOverlay) {
+ iconOverlayView.playAnimation()
+ }
+ viewModel.setPreviousIconOverlayWasError(showingError)
+ }
+ }
+ }
+
+ launch {
+ viewModel.shouldFlipIconView.collect { shouldFlipIconView ->
+ if (shouldFlipIconView) {
+ iconView.rotation = 180f
+ }
+ }
+ }
+
+ launch {
+ viewModel.contentDescriptionId.collect { id ->
+ if (id != -1) {
+ iconView.contentDescription = iconView.context.getString(id)
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
deleted file mode 100644
index dfd3a9b8aebe..000000000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.ui.viewmodel
-
-import android.annotation.RawRes
-import android.content.res.Configuration
-import com.android.systemui.res.R
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.shared.model.DisplayRotation
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-
-/** Models UI of [BiometricPromptLayout.iconView] */
-class PromptFingerprintIconViewModel
-@Inject
-constructor(
- private val displayStateInteractor: DisplayStateInteractor,
- promptSelectorInteractor: PromptSelectorInteractor,
-) {
- /** Current BiometricPromptLayout.iconView asset. */
- val iconAsset: Flow<Int> =
- combine(
- displayStateInteractor.currentRotation,
- displayStateInteractor.isFolded,
- displayStateInteractor.isInRearDisplayMode,
- promptSelectorInteractor.sensorType,
- ) {
- rotation: DisplayRotation,
- isFolded: Boolean,
- isInRearDisplayMode: Boolean,
- sensorType: FingerprintSensorType ->
- when (sensorType) {
- FingerprintSensorType.POWER_BUTTON ->
- getSideFpsAnimationAsset(rotation, isFolded, isInRearDisplayMode)
- // Replace below when non-SFPS iconAsset logic is migrated to this ViewModel
- else -> -1
- }
- }
-
- @RawRes
- private fun getSideFpsAnimationAsset(
- rotation: DisplayRotation,
- isDeviceFolded: Boolean,
- isInRearDisplayMode: Boolean,
- ): Int =
- when (rotation) {
- DisplayRotation.ROTATION_90 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_reverse_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_topleft
- } else {
- R.raw.biometricprompt_portrait_base_topleft
- }
- DisplayRotation.ROTATION_270 ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_portrait_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_bottomright
- } else {
- R.raw.biometricprompt_portrait_base_bottomright
- }
- else ->
- if (isInRearDisplayMode) {
- R.raw.biometricprompt_rear_landscape_base
- } else if (isDeviceFolded) {
- R.raw.biometricprompt_folded_base_default
- } else {
- R.raw.biometricprompt_landscape_base
- }
- }
-
- /** Called on configuration changes */
- fun onConfigurationChanged(newConfig: Configuration) {
- displayStateInteractor.onConfigurationChanged(newConfig)
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
new file mode 100644
index 000000000000..11a5d8b578df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.RawRes
+import android.content.res.Configuration
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
+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.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
+ */
+class PromptIconViewModel
+constructor(
+ promptViewModel: PromptViewModel,
+ private val displayStateInteractor: DisplayStateInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor
+) {
+
+ /** Auth types for the UI to display. */
+ enum class AuthType {
+ Fingerprint,
+ Face,
+ Coex
+ }
+
+ /**
+ * Indicates what auth type the UI currently displays.
+ * Fingerprint-only auth -> Fingerprint
+ * Face-only auth -> Face
+ * Co-ex auth, implicit flow -> Face
+ * Co-ex auth, explicit flow -> Coex
+ */
+ val activeAuthType: Flow<AuthType> =
+ combine(
+ promptViewModel.modalities.distinctUntilChanged(),
+ promptViewModel.faceMode.distinctUntilChanged()
+ ) { modalities, faceMode ->
+ if (modalities.hasFaceAndFingerprint && !faceMode) {
+ AuthType.Coex
+ } else if (modalities.hasFaceOnly || faceMode) {
+ AuthType.Face
+ } else if (modalities.hasFingerprintOnly) {
+ AuthType.Fingerprint
+ } else {
+ throw IllegalStateException("unexpected modality: $modalities")
+ }
+ }
+
+ /** Whether an error message is currently being shown. */
+ val showingError = promptViewModel.showingError
+
+ /** Whether the previous icon shown displayed an error. */
+ private val _previousIconWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Whether the previous icon overlay shown displayed an error. */
+ private val _previousIconOverlayWasError: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ fun setPreviousIconWasError(previousIconWasError: Boolean) {
+ _previousIconWasError.value = previousIconWasError
+ }
+
+ fun setPreviousIconOverlayWasError(previousIconOverlayWasError: Boolean) {
+ _previousIconOverlayWasError.value = previousIconOverlayWasError
+ }
+
+ /** Called when iconView begins animating. */
+ fun onAnimationStart() {
+ _animationEnded.value = false
+ }
+
+ /** Called when iconView ends animating. */
+ fun onAnimationEnd() {
+ _animationEnded.value = true
+ }
+
+ private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /**
+ * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+ * ended).
+ */
+ val shouldPulseAnimation: Flow<Boolean> =
+ combine(_animationEnded, promptViewModel.isAuthenticating) {
+ animationEnded,
+ isAuthenticating ->
+ animationEnded && isAuthenticating
+ }
+ .distinctUntilChanged()
+
+ private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
+ /** Layout params for fingerprint iconView */
+ val fingerprintIconWidth: Int = promptViewModel.fingerprintIconWidth
+ val fingerprintIconHeight: Int = promptViewModel.fingerprintIconHeight
+
+ /** Layout params for face iconView */
+ val faceIconWidth: Int = promptViewModel.faceIconWidth
+ val faceIconHeight: Int = promptViewModel.faceIconHeight
+
+ /** Current BiometricPromptLayout.iconView asset. */
+ val iconAsset: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint ->
+ combine(
+ displayStateInteractor.currentRotation,
+ displayStateInteractor.isFolded,
+ displayStateInteractor.isInRearDisplayMode,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ rotation: DisplayRotation,
+ isFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ else ->
+ getFingerprintIconViewAsset(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ }
+ }
+ AuthType.Face ->
+ shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+ if (shouldPulseAnimation) {
+ val iconAsset =
+ if (_lastPulseLightToDark.value) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+ flowOf(iconAsset)
+ } else {
+ combine(
+ promptViewModel.isAuthenticated.distinctUntilChanged(),
+ promptViewModel.isAuthenticating.distinctUntilChanged(),
+ promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+ promptViewModel.showingError.distinctUntilChanged()
+ ) {
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ getFaceIconViewAsset(
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ AuthType.Coex ->
+ combine(
+ displayStateInteractor.currentRotation,
+ displayStateInteractor.isFolded,
+ displayStateInteractor.isInRearDisplayMode,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError,
+ ) {
+ rotation: DisplayRotation,
+ isFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconViewAsset(rotation, isFolded, isInRearDisplayMode)
+ else ->
+ getCoexIconViewAsset(
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ }
+
+ private fun getFingerprintIconViewAsset(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_success_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
+ } else if (showingError) {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ } else {
+ -1
+ }
+
+ @RawRes
+ private fun getSfpsIconViewAsset(
+ rotation: DisplayRotation,
+ isDeviceFolded: Boolean,
+ isInRearDisplayMode: Boolean,
+ ): Int =
+ when (rotation) {
+ DisplayRotation.ROTATION_90 ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_portrait_reverse_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_topleft
+ } else {
+ R.raw.biometricprompt_portrait_base_topleft
+ }
+ DisplayRotation.ROTATION_270 ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_portrait_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_bottomright
+ } else {
+ R.raw.biometricprompt_portrait_base_bottomright
+ }
+ else ->
+ if (isInRearDisplayMode) {
+ R.raw.biometricprompt_rear_landscape_base
+ } else if (isDeviceFolded) {
+ R.raw.biometricprompt_folded_base_default
+ } else {
+ R.raw.biometricprompt_landscape_base
+ }
+ }
+
+ @DrawableRes
+ private fun getFaceIconViewAsset(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticated && isPendingConfirmation) {
+ R.drawable.face_dialog_wink_from_dark
+ } else if (authState.isAuthenticated) {
+ R.drawable.face_dialog_dark_to_checkmark
+ } else if (isAuthenticating) {
+ _lastPulseLightToDark.value = false
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else if (showingError) {
+ R.drawable.face_dialog_dark_to_error
+ } else if (_previousIconWasError.value) {
+ R.drawable.face_dialog_error_to_idle
+ } else {
+ R.drawable.face_dialog_idle_static
+ }
+
+ @RawRes
+ private fun getCoexIconViewAsset(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+ } else if (isPendingConfirmation) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_unlock_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
+ }
+ } else if (authState.isAuthenticated) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_success_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconWasError.value) {
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie
+ } else {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ }
+ } else if (showingError) {
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie
+ } else {
+ -1
+ }
+
+ /** Current BiometricPromptLayout.biometric_icon_overlay asset. */
+ var iconOverlayAsset: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ displayStateInteractor.currentRotation,
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ rotation: DisplayRotation,
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ getSfpsIconOverlayAsset(
+ rotation,
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else -> -1
+ }
+ }
+ AuthType.Face -> flowOf(-1)
+ }
+ }
+
+ @RawRes
+ private fun getSfpsIconOverlayAsset(
+ rotation: DisplayRotation,
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isAuthenticated) {
+ if (_previousIconOverlayWasError.value) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+ }
+ }
+ } else if (isAuthenticating) {
+ if (_previousIconOverlayWasError.value) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+ }
+ } else {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ }
+ }
+ } else if (showingError) {
+ when (rotation) {
+ DisplayRotation.ROTATION_0 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 -> R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ }
+ } else {
+ -1
+ }
+
+ /** Content description for iconView */
+ val contentDescriptionId: Flow<Int> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ getFingerprintIconContentDescriptionId(
+ sensorType,
+ authState.isAuthenticated,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ AuthType.Face ->
+ combine(
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError,
+ ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+ ->
+ getFaceIconContentDescriptionId(authState, isAuthenticating, showingError)
+ }
+ }
+ }
+
+ private fun getFingerprintIconContentDescriptionId(
+ sensorType: FingerprintSensorType,
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (isPendingConfirmation) {
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ else -> R.string.fingerprint_dialog_authenticated_confirmation
+ }
+ } else if (isAuthenticating || isAuthenticated) {
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ R.string.security_settings_sfps_enroll_find_sensor_message
+ else -> R.string.fingerprint_dialog_touch_sensor
+ }
+ } else if (showingError) {
+ R.string.biometric_dialog_try_again
+ } else {
+ -1
+ }
+
+ private fun getFaceIconContentDescriptionId(
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ): Int =
+ if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+ R.string.biometric_dialog_face_icon_description_confirmed
+ } else if (authState.isAuthenticated) {
+ R.string.biometric_dialog_face_icon_description_authenticated
+ } else if (isAuthenticating) {
+ R.string.biometric_dialog_face_icon_description_authenticating
+ } else if (showingError) {
+ R.string.keyguard_face_failed
+ } else {
+ R.string.biometric_dialog_face_icon_description_idle
+ }
+
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be playing. */
+ val shouldAnimateIconView: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else ->
+ shouldAnimateFingerprintIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ }
+ }
+ AuthType.Face ->
+ combine(
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) { authState: PromptAuthState, isAuthenticating: Boolean, showingError: Boolean
+ ->
+ isAuthenticating ||
+ authState.isAuthenticated ||
+ showingError ||
+ _previousIconWasError.value
+ }
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.isPendingConfirmation,
+ promptViewModel.showingError,
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else ->
+ shouldAnimateCoexIconView(
+ authState.isAuthenticated,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
+ }
+ }
+
+ private fun shouldAnimateFingerprintIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = (isAuthenticating && _previousIconWasError.value) || isAuthenticated || showingError
+
+ private fun shouldAnimateSfpsIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = isAuthenticated || isAuthenticating || showingError
+
+ private fun shouldAnimateCoexIconView(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean
+ ) =
+ (isAuthenticating && _previousIconWasError.value) ||
+ isPendingConfirmation ||
+ isAuthenticated ||
+ showingError
+
+ /** Whether the current iconOverlayAsset animation should be playing. */
+ val shouldAnimateIconOverlay: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ promptViewModel.isAuthenticated,
+ promptViewModel.isAuthenticating,
+ promptViewModel.showingError
+ ) {
+ sensorType: FingerprintSensorType,
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ showingError: Boolean ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ shouldAnimateSfpsIconOverlay(
+ authState.isAuthenticated,
+ isAuthenticating,
+ showingError
+ )
+ else -> false
+ }
+ }
+ AuthType.Face -> flowOf(false)
+ }
+ }
+
+ private fun shouldAnimateSfpsIconOverlay(
+ isAuthenticated: Boolean,
+ isAuthenticating: Boolean,
+ showingError: Boolean
+ ) = (isAuthenticating && _previousIconOverlayWasError.value) || isAuthenticated || showingError
+
+ /** Whether the iconView should be flipped due to a device using reverse default rotation . */
+ val shouldFlipIconView: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex ->
+ combine(
+ promptSelectorInteractor.sensorType,
+ displayStateInteractor.currentRotation
+ ) { sensorType: FingerprintSensorType, rotation: DisplayRotation ->
+ when (sensorType) {
+ FingerprintSensorType.POWER_BUTTON ->
+ (rotation == DisplayRotation.ROTATION_180)
+ else -> false
+ }
+ }
+ AuthType.Face -> flowOf(false)
+ }
+ }
+
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+ val shouldRepeatAnimation: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> flowOf(false)
+ AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+ }
+ }
+
+ /** Called on configuration changes */
+ fun onConfigurationChanged(newConfig: Configuration) {
+ displayStateInteractor.onConfigurationChanged(newConfig)
+ }
+
+ /** iconView assets for caching */
+ fun getRawAssets(hasSfps: Boolean): List<Int> {
+ return if (hasSfps) {
+ listOf(
+ R.raw.biometricprompt_fingerprint_to_error_landscape,
+ R.raw.biometricprompt_folded_base_bottomright,
+ R.raw.biometricprompt_folded_base_default,
+ R.raw.biometricprompt_folded_base_topleft,
+ R.raw.biometricprompt_landscape_base,
+ R.raw.biometricprompt_portrait_base_bottomright,
+ R.raw.biometricprompt_portrait_base_topleft,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft,
+ R.raw.biometricprompt_symbol_error_to_success_landscape,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_error_to_success_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright,
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ )
+ } else {
+ listOf(
+ R.raw.fingerprint_dialogue_error_to_fingerprint_lottie,
+ R.raw.fingerprint_dialogue_error_to_success_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_error_lottie,
+ R.raw.fingerprint_dialogue_fingerprint_to_success_lottie
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 267afae118e6..e49b4a7bbce9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -28,10 +28,10 @@ import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.PromptKind
-import com.android.systemui.biometrics.ui.binder.Spaghetti
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.Job
@@ -39,7 +39,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -51,25 +50,29 @@ import kotlinx.coroutines.launch
class PromptViewModel
@Inject
constructor(
- private val displayStateInteractor: DisplayStateInteractor,
- private val promptSelectorInteractor: PromptSelectorInteractor,
+ displayStateInteractor: DisplayStateInteractor,
+ promptSelectorInteractor: PromptSelectorInteractor,
private val vibrator: VibratorHelper,
@Application context: Context,
private val featureFlags: FeatureFlags,
) {
- /** Models UI of [BiometricPromptLayout.iconView] */
- val fingerprintIconViewModel: PromptFingerprintIconViewModel =
- PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
-
/** The set of modalities available for this prompt */
val modalities: Flow<BiometricModalities> =
promptSelectorInteractor.prompt
.map { it?.modalities ?: BiometricModalities() }
.distinctUntilChanged()
- // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
- private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE)
- val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow()
+ /** Layout params for fingerprint iconView */
+ val fingerprintIconWidth: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_width)
+ val fingerprintIconHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_fingerprint_icon_height)
+
+ /** Layout params for face iconView */
+ val faceIconWidth: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
+ val faceIconHeight: Int =
+ context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
@@ -82,6 +85,12 @@ constructor(
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
+ /** If the auth is pending confirmation. */
+ val isPendingConfirmation: Flow<Boolean> =
+ isAuthenticated.map { authState ->
+ authState.isAuthenticated && authState.needsUserConfirmation
+ }
+
private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
/** The kind of credential the user has. */
@@ -96,6 +105,9 @@ constructor(
/** A message to show the user, if there is an error, hint, or help to show. */
val message: Flow<PromptMessage> = _message.asStateFlow()
+ /** Whether an error message is currently being shown. */
+ val showingError: Flow<Boolean> = message.map { it.isError }.distinctUntilChanged()
+
private val isRetrySupported: Flow<Boolean> = modalities.map { it.hasFace }
private val _fingerprintStartMode = MutableStateFlow(FingerprintStartMode.Pending)
@@ -141,6 +153,38 @@ constructor(
!isOverlayTouched && size.isNotSmall
}
+ /**
+ * When fingerprint and face modalities are enrolled, indicates whether only face auth has
+ * started.
+ *
+ * True when fingerprint and face modalities are enrolled and implicit flow is active. This
+ * occurs in co-ex auth when confirmation is not required and only face auth is started, then
+ * becomes false when device transitions to explicit flow after a first error, when the
+ * fingerprint sensor is started.
+ *
+ * False when the dialog opens in explicit flow (fingerprint and face modalities enrolled but
+ * confirmation is required), or if user has only fingerprint enrolled, or only face enrolled.
+ */
+ val faceMode: Flow<Boolean> =
+ combine(modalities, isConfirmationRequired, fingerprintStartMode) {
+ modalities: BiometricModalities,
+ isConfirmationRequired: Boolean,
+ fingerprintStartMode: FingerprintStartMode ->
+ if (modalities.hasFaceAndFingerprint) {
+ if (isConfirmationRequired) {
+ false
+ } else {
+ !fingerprintStartMode.isStarted
+ }
+ } else {
+ false
+ }
+ }
+ .distinctUntilChanged()
+
+ val iconViewModel: PromptIconViewModel =
+ PromptIconViewModel(this, displayStateInteractor, promptSelectorInteractor)
+
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -184,9 +228,9 @@ constructor(
val isConfirmButtonVisible: Flow<Boolean> =
combine(
size,
- isAuthenticated,
- ) { size, authState ->
- size.isNotSmall && authState.isAuthenticated && authState.needsUserConfirmation
+ isPendingConfirmation,
+ ) { size, isPendingConfirmation ->
+ size.isNotSmall && isPendingConfirmation
}
.distinctUntilChanged()
@@ -293,7 +337,6 @@ constructor(
_isAuthenticated.value = PromptAuthState(false)
_forceMediumSize.value = true
_message.value = PromptMessage.Error(message)
- _legacyState.value = Spaghetti.BiometricState.STATE_ERROR
if (hapticFeedback) {
vibrator.error(failedModality)
@@ -305,7 +348,7 @@ constructor(
if (authenticateAfterError) {
showAuthenticating(messageAfterError)
} else {
- showInfo(messageAfterError)
+ showHelp(messageAfterError)
}
}
}
@@ -325,15 +368,12 @@ constructor(
private fun supportsRetry(failedModality: BiometricModality) =
failedModality == BiometricModality.Face
- suspend fun showHelp(message: String) = showHelp(message, clearIconError = false)
- suspend fun showInfo(message: String) = showHelp(message, clearIconError = true)
-
/**
* Show a persistent help message.
*
* Will be show even if the user has already authenticated.
*/
- private suspend fun showHelp(message: String, clearIconError: Boolean) {
+ suspend fun showHelp(message: String) {
val alreadyAuthenticated = _isAuthenticated.value.isAuthenticated
if (!alreadyAuthenticated) {
_isAuthenticating.value = false
@@ -343,16 +383,6 @@ constructor(
_message.value =
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
- _legacyState.value =
- if (alreadyAuthenticated && isConfirmationRequired.first()) {
- Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
- } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
- Spaghetti.BiometricState.STATE_AUTHENTICATED
- } else if (clearIconError) {
- Spaghetti.BiometricState.STATE_IDLE
- } else {
- Spaghetti.BiometricState.STATE_HELP
- }
messageJob?.cancel()
messageJob = null
@@ -376,7 +406,6 @@ constructor(
_message.value =
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
- _legacyState.value = Spaghetti.BiometricState.STATE_HELP
messageJob?.cancel()
messageJob = launch {
@@ -396,7 +425,6 @@ constructor(
_isAuthenticating.value = true
_isAuthenticated.value = PromptAuthState(false)
_message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
- _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING
// reset the try again button(s) after the user attempts a retry
if (isRetry) {
@@ -427,12 +455,6 @@ constructor(
_isAuthenticated.value =
PromptAuthState(true, modality, needsUserConfirmation, dismissAfterDelay)
_message.value = PromptMessage.Empty
- _legacyState.value =
- if (needsUserConfirmation) {
- Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
- } else {
- Spaghetti.BiometricState.STATE_AUTHENTICATED
- }
if (!needsUserConfirmation) {
vibrator.success(modality)
@@ -472,7 +494,6 @@ constructor(
_isAuthenticated.value = authState.asExplicitlyConfirmed()
_message.value = PromptMessage.Empty
- _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED
vibrator.success(authState.authenticatedModality)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 2bd62587834d..21578f491de7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -146,6 +146,16 @@ constructor(
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
fun show(isScrimmed: Boolean) {
+ if (primaryBouncerView.delegate == null) {
+ Log.d(
+ TAG,
+ "PrimaryBouncerInteractor#show is being called before the " +
+ "primaryBouncerDelegate is set. Let's exit early so we don't set the wrong " +
+ "primaryBouncer state."
+ )
+ return
+ }
+
// Reset some states as we show the bouncer.
repository.setKeyguardAuthenticatedBiometrics(null)
repository.setPrimaryStartingToHide(false)
diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
index 0bf50693344a..7d73896bb370 100644
--- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java
@@ -19,7 +19,6 @@ package com.android.systemui.colorextraction;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Color;
import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
@@ -47,9 +46,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
ConfigurationController.ConfigurationListener {
private static final String TAG = "SysuiColorExtractor";
private final Tonal mTonal;
- private boolean mHasMediaArtwork;
private final GradientColors mNeutralColorsLock;
- private final GradientColors mBackdropColors;
private Lazy<SelectedUserInteractor> mUserInteractor;
@Inject
@@ -82,9 +79,6 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
mNeutralColorsLock = new GradientColors();
configurationController.addCallback(this);
dumpManager.registerDumpable(getClass().getSimpleName(), this);
-
- mBackdropColors = new GradientColors();
- mBackdropColors.setMainColor(Color.BLACK);
mUserInteractor = userInteractor;
// Listen to all users instead of only the current one.
@@ -123,14 +117,6 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
triggerColorsChanged(WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
}
- @Override
- public GradientColors getColors(int which, int type) {
- if (mHasMediaArtwork && (which & WallpaperManager.FLAG_LOCK) != 0) {
- return mBackdropColors;
- }
- return super.getColors(which, type);
- }
-
/**
* Colors that should be using for scrims.
*
@@ -140,14 +126,7 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
* - Black otherwise
*/
public GradientColors getNeutralColors() {
- return mHasMediaArtwork ? mBackdropColors : mNeutralColorsLock;
- }
-
- public void setHasMediaArtwork(boolean hasBackdrop) {
- if (mHasMediaArtwork != hasBackdrop) {
- mHasMediaArtwork = hasBackdrop;
- triggerColorsChanged(WallpaperManager.FLAG_LOCK);
- }
+ return mNeutralColorsLock;
}
@Override
@@ -164,7 +143,5 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable,
pw.println(" system: " + Arrays.toString(system));
pw.println(" lock: " + Arrays.toString(lock));
pw.println(" Neutral colors: " + mNeutralColorsLock);
- pw.println(" Has media backdrop: " + mHasMediaArtwork);
-
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
new file mode 100644
index 000000000000..8d5b84fea9ab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationState.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+package com.android.systemui.common.ui
+
+import android.content.Context
+import androidx.annotation.AttrRes
+import androidx.annotation.ColorInt
+import androidx.annotation.DimenRes
+import com.android.settingslib.Utils
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onThemeChanged
+import com.android.systemui.util.kotlin.emitOnStart
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Configuration-aware-state-tracking utilities. */
+class ConfigurationState
+@Inject
+constructor(
+ private val configurationController: ConfigurationController,
+ @Application private val context: Context,
+) {
+ /**
+ * Returns a [Flow] that emits a dimension pixel size that is kept in sync with the device
+ * configuration.
+ *
+ * @see android.content.res.Resources.getDimensionPixelSize
+ */
+ fun getDimensionPixelSize(@DimenRes id: Int): Flow<Int> {
+ return configurationController.onDensityOrFontScaleChanged.emitOnStart().map {
+ context.resources.getDimensionPixelSize(id)
+ }
+ }
+
+ /**
+ * Returns a [Flow] that emits a color that is kept in sync with the device theme.
+ *
+ * @see Utils.getColorAttrDefaultColor
+ */
+ fun getColorAttr(@AttrRes id: Int, @ColorInt defaultValue: Int): Flow<Int> {
+ return configurationController.onThemeChanged.emitOnStart().map {
+ Utils.getColorAttrDefaultColor(context, id, defaultValue)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt
new file mode 100644
index 000000000000..b0e69317e0ee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/CommonUiDataLayerModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.common.ui.data
+
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule
+import dagger.Module
+
+@Module(includes = [ConfigurationRepositoryModule::class]) object CommonUiDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index b8de8d8226a6..e44927432719 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -13,18 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.common.ui.data.repository
import android.content.Context
import android.content.res.Configuration
import android.view.DisplayInfo
+import androidx.annotation.DimenRes
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
+import dagger.Binds
+import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,7 +52,6 @@ interface ConfigurationRepository {
fun getDimensionPixelSize(id: Int): Int
}
-@ExperimentalCoroutinesApi
@SysUISingleton
class ConfigurationRepositoryImpl
@Inject
@@ -119,7 +122,12 @@ constructor(
return 1f
}
- override fun getDimensionPixelSize(id: Int): Int {
+ override fun getDimensionPixelSize(@DimenRes id: Int): Int {
return context.resources.getDimensionPixelSize(id)
}
}
+
+@Module
+interface ConfigurationRepositoryModule {
+ @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
index f9c4f29afee9..1a214ba5a3d9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetMetadata.kt
@@ -16,7 +16,7 @@
package com.android.systemui.communal.data.model
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
/** Metadata for the default widgets */
data class CommunalWidgetMetadata(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 53b6879db3d7..485e5122cfad 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -1,5 +1,6 @@
package com.android.systemui.communal.data.repository
+import com.android.systemui.FeatureFlags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -15,10 +16,11 @@ interface CommunalRepository {
class CommunalRepositoryImpl
@Inject
constructor(
- private val featureFlags: FeatureFlagsClassic,
+ private val featureFlags: FeatureFlags,
+ private val featureFlagsClassic: FeatureFlagsClassic,
) : CommunalRepository {
override val isCommunalEnabled: Boolean
get() =
- featureFlags.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
- featureFlags.isEnabled(Flags.COMMUNAL_HUB)
+ featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) &&
+ featureFlags.communalHub()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index f13b62fbfeb9..77025dc8839a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.BroadcastReceiver
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -28,11 +29,12 @@ import android.os.UserManager
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -43,6 +45,7 @@ import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
/** Encapsulates the state of widgets for communal mode. */
@@ -52,6 +55,9 @@ interface CommunalWidgetRepository {
/** Widgets that are allowed to render in the glanceable hub */
val communalWidgetAllowlist: List<CommunalWidgetMetadata>
+
+ /** A flow of information about all the communal widgets to show. */
+ val communalWidgets: Flow<List<CommunalWidgetContentModel>>
}
@SysUISingleton
@@ -67,7 +73,7 @@ constructor(
private val userManager: UserManager,
private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
) : CommunalWidgetRepository {
companion object {
const val TAG = "CommunalWidgetRepository"
@@ -88,49 +94,59 @@ constructor(
// Widgets that should be rendered in communal mode.
private val widgets: HashMap<Int, CommunalAppWidgetInfo> = hashMapOf()
- private val isUserUnlocked: Flow<Boolean> = callbackFlow {
- if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
- awaitClose()
- }
-
- fun isUserUnlockingOrUnlocked(): Boolean {
- return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
- }
-
- fun send() {
- trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
- }
+ private val isUserUnlocked: Flow<Boolean> =
+ callbackFlow {
+ if (!communalRepository.isCommunalEnabled) {
+ awaitClose()
+ }
- if (isUserUnlockingOrUnlocked()) {
- send()
- awaitClose()
- } else {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- send()
- }
+ fun isUserUnlockingOrUnlocked(): Boolean {
+ return userManager.isUserUnlockingOrUnlocked(userTracker.userHandle)
}
- broadcastDispatcher.registerReceiver(
- receiver,
- IntentFilter(Intent.ACTION_USER_UNLOCKED),
- )
+ fun send() {
+ trySendWithFailureLogging(isUserUnlockingOrUnlocked(), TAG)
+ }
- awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+ if (isUserUnlockingOrUnlocked()) {
+ send()
+ awaitClose()
+ } else {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ send()
+ }
+ }
+
+ broadcastDispatcher.registerReceiver(
+ receiver,
+ IntentFilter(Intent.ACTION_USER_UNLOCKED),
+ )
+
+ awaitClose { broadcastDispatcher.unregisterReceiver(receiver) }
+ }
+ }
+ .distinctUntilChanged()
+
+ private val isHostActive: Flow<Boolean> =
+ isUserUnlocked.map {
+ if (it) {
+ startListening()
+ true
+ } else {
+ stopListening()
+ clearWidgets()
+ false
+ }
}
- }
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> =
- isUserUnlocked.map { isUserUnlocked ->
- if (!isUserUnlocked) {
- clearWidgets()
- stopListening()
+ isHostActive.map { isHostActive ->
+ if (!isHostActive || !featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
return@map null
}
- startListening()
-
val providerInfo =
appWidgetManager.installedProviders.find {
it.loadLabel(packageManager).equals(WIDGET_LABEL)
@@ -144,6 +160,42 @@ constructor(
return@map addWidget(providerInfo)
}
+ override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+ isHostActive.map { isHostActive ->
+ if (!isHostActive) {
+ return@map emptyList()
+ }
+
+ // The allowlist should be fetched from the local database with all the metadata tied to
+ // a widget, including an appWidgetId if it has been bound. Before the database is set
+ // up, we are going to use the app widget host as the source of truth for bound widgets,
+ // and rebind each time on boot.
+
+ // Remove all previously bound widgets.
+ appWidgetHost.appWidgetIds.forEach { appWidgetHost.deleteAppWidgetId(it) }
+
+ val inventory = mutableListOf<CommunalWidgetContentModel>()
+
+ // Bind all widgets from the allowlist.
+ communalWidgetAllowlist.forEach {
+ val id = appWidgetHost.allocateAppWidgetId()
+ appWidgetManager.bindAppWidgetId(
+ id,
+ ComponentName.unflattenFromString(it.componentName),
+ )
+
+ inventory.add(
+ CommunalWidgetContentModel(
+ appWidgetId = id,
+ providerInfo = appWidgetManager.getAppWidgetInfo(id),
+ priority = it.priority,
+ )
+ )
+ }
+
+ return@map inventory.toList()
+ }
+
private fun getWidgetAllowlist(): List<CommunalWidgetMetadata> {
val componentNames =
applicationContext.resources.getStringArray(R.array.config_communalWidgetAllowlist)
@@ -151,7 +203,7 @@ constructor(
CommunalWidgetMetadata(
componentName = name,
priority = componentNames.size - index,
- sizes = listOf(CommunalContentSize.HALF)
+ sizes = listOf(CommunalContentSize.HALF),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 04bb6ae75e60..62387079ab35 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -18,7 +18,8 @@ package com.android.systemui.communal.domain.interactor
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -38,4 +39,12 @@ constructor(
/** A flow of info about the widget to be displayed, or null if widget is unavailable. */
val appWidgetInfo: Flow<CommunalAppWidgetInfo?> = widgetRepository.stopwatchAppWidgetInfo
+
+ /**
+ * A flow of information about widgets to be shown in communal hub.
+ *
+ * Currently only showing persistent widgets that have been bound to the app widget service
+ * (have an allocated id).
+ */
+ val widgetContent: Flow<List<CommunalWidgetContentModel>> = widgetRepository.communalWidgets
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
deleted file mode 100644
index 0bd7d86c972d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalContentSize.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.android.systemui.communal.shared
-
-/** Supported sizes for communal content in the layout grid. */
-enum class CommunalContentSize {
- FULL,
- HALF,
- THIRD,
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
index 0803a01b93b8..109ed2da7e48 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalAppWidgetInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalAppWidgetInfo.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.communal.shared
+package com.android.systemui.communal.shared.model
import android.appwidget.AppWidgetProviderInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
new file mode 100644
index 000000000000..7f05b9cd4943
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentCategory.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.shared.model
+
+enum class CommunalContentCategory {
+ /** The content persists in the communal hub until removed by the user. */
+ PERSISTENT,
+
+ /** The content temporarily shows up in the communal hub when certain conditions are met. */
+ TRANSIENT,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
new file mode 100644
index 000000000000..39a6476929ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.shared.model
+
+/** Supported sizes for communal content in the layout grid. */
+enum class CommunalContentSize {
+ /** Content takes the full height of the column. */
+ FULL,
+
+ /** Content takes half of the height of the column. */
+ HALF,
+
+ /** Content takes a third of the height of the column. */
+ THIRD,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
new file mode 100644
index 000000000000..e141dc40477c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.communal.shared.model
+
+import android.appwidget.AppWidgetProviderInfo
+
+/** Encapsulates data for a communal widget. */
+data class CommunalWidgetContentModel(
+ val appWidgetId: Int,
+ val providerInfo: AppWidgetProviderInfo,
+ val priority: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
index 2a08d7f6bc28..0daf7b5610e8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/adapter/CommunalWidgetViewAdapter.kt
@@ -20,7 +20,7 @@ import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.Context
import android.util.SizeF
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.communal.ui.view.CommunalWidgetWrapper
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.LogBuffer
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
new file mode 100644
index 000000000000..98060dc1dceb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/model/CommunalContentUiModel.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.communal.ui.model
+
+import android.view.View
+import com.android.systemui.communal.shared.model.CommunalContentSize
+
+/**
+ * Encapsulates data for a communal content that holds a view.
+ *
+ * This model stays in the UI layer.
+ */
+data class CommunalContentUiModel(
+ val view: View,
+ val size: CommunalContentSize,
+ val priority: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index ddeb1d67b945..25c64eafe255 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -16,17 +16,43 @@
package com.android.systemui.communal.ui.viewmodel
+import android.appwidget.AppWidgetHost
+import android.content.Context
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.model.CommunalContentUiModel
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
@SysUISingleton
class CommunalViewModel
@Inject
constructor(
+ @Application private val context: Context,
+ private val appWidgetHost: AppWidgetHost,
+ communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
) {
/** Whether communal hub should show tutorial content. */
val showTutorialContent: Flow<Boolean> = tutorialInteractor.isTutorialAvailable
+
+ /** List of widgets to be displayed in the communal hub. */
+ val widgetContent: Flow<List<CommunalContentUiModel>> =
+ communalInteractor.widgetContent.map {
+ it.map {
+ // TODO(b/306406256): As adding and removing widgets functionalities are
+ // supported, cache the host views so they're not recreated each time.
+ val hostView = appWidgetHost.createView(context, it.appWidgetId, it.providerInfo)
+ return@map CommunalContentUiModel(
+ view = hostView,
+ priority = it.priority,
+ // All widgets have HALF size.
+ size = CommunalContentSize.HALF,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
index 8fba342c49be..d7bbea64332e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalWidgetViewModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.communal.ui.viewmodel
import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
index 20b2494a6c29..f7b6b0f06a00 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationLayoutEngine.java
@@ -652,8 +652,7 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll
CrossFadeHelper.fadeOut(
mLayout,
mFadeOutDuration,
- /* delay= */ 0,
- /* endRunnable= */ null);
+ /* delay= */ 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 9221832b4ba1..4bdea75d9d71 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -79,4 +79,7 @@ interface BaseComposeFacade {
context: Context,
viewModel: CommunalViewModel,
): View
+
+ /** Creates a container that hosts the communal UI and handles gesture transitions. */
+ fun createCommunalContainer(context: Context, viewModel: CommunalViewModel): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 5d6949b3e87f..d8ff535ffd78 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -57,7 +57,6 @@ import com.android.systemui.statusbar.ImmersiveModeConfirmation
import com.android.systemui.statusbar.gesture.GesturePointerEventListener
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
-import com.android.systemui.statusbar.phone.LockscreenWallpaper
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
import com.android.systemui.stylus.StylusUsiPowerStartable
@@ -344,11 +343,6 @@ abstract class SystemUICoreStartableModule {
@Binds
@IntoMap
- @ClassKey(LockscreenWallpaper::class)
- abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable
-
- @Binds
- @IntoMap
@ClassKey(ScrimController::class)
abstract fun bindScrimController(impl: ScrimController): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index f0d7592d8940..04b2852db9e2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,7 +41,7 @@ import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule;
import com.android.systemui.bouncer.ui.BouncerViewModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
-import com.android.systemui.common.ui.data.repository.CommonRepositoryModule;
+import com.android.systemui.common.ui.data.CommonUiDataLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
@@ -170,7 +170,7 @@ import javax.inject.Named;
BouncerViewModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
- CommonRepositoryModule.class,
+ CommonUiDataLayerModule.class,
CommunalModule.class,
ConnectivityModule.class,
ControlsModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 4f166858faf8..3e2ecee1fe3e 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -47,6 +47,8 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
/** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
@@ -54,10 +56,7 @@ interface DisplayRepository {
/** Display change event indicating a change to the given displayId has occurred. */
val displayChangeEvent: Flow<Int>
- /**
- * Provides a nullable set of displays. Updates when new displays have been added or removed but
- * not when a display's info has changed.
- */
+ /** Provides the current set of displays. */
val displays: Flow<Set<Display>>
/**
@@ -112,10 +111,6 @@ constructor(
trySend(DisplayEvent.Changed(displayId))
}
}
- // Triggers an initial event when subscribed. This is needed to avoid getDisplays to
- // be called when this class is constructed, but only when someone subscribes to
- // this flow.
- trySend(DisplayEvent.Changed(Display.DEFAULT_DISPLAY))
displayManager.registerDisplayListener(
callback,
backgroundHandler,
@@ -125,6 +120,7 @@ constructor(
)
awaitClose { displayManager.unregisterDisplayListener(callback) }
}
+ .onStart { emit(DisplayEvent.Changed(Display.DEFAULT_DISPLAY)) }
.flowOn(backgroundCoroutineDispatcher)
override val displayChangeEvent: Flow<Int> =
@@ -134,13 +130,9 @@ constructor(
allDisplayEvents
.map { getDisplays() }
.flowOn(backgroundCoroutineDispatcher)
- .stateIn(
- applicationScope,
- started = SharingStarted.WhileSubscribed(),
- // To avoid getting displays on this object construction, they are get after the
- // first event. allDisplayEvents emits a changed event when we subscribe to it.
- initialValue = emptySet()
- )
+ .shareIn(applicationScope, started = SharingStarted.WhileSubscribed(), replay = 1)
+
+ override val displays: Flow<Set<Display>> = enabledDisplays
private fun getDisplays(): Set<Display> =
traceSection("DisplayRepository#getDisplays()") {
@@ -148,8 +140,6 @@ constructor(
}
/** Propagate to the listeners only enabled displays */
- override val displays: Flow<Set<Display>> = enabledDisplays
-
private val enabledDisplayIds: Flow<Set<Int>> =
enabledDisplays
.map { enabledDisplaysSet -> enabledDisplaysSet.map { it.displayId }.toSet() }
@@ -251,6 +241,7 @@ constructor(
val id = pendingDisplayIds.maxOrNull() ?: return@map null
object : DisplayRepository.PendingDisplay {
override val id = id
+
override suspend fun enable() {
traceSection("DisplayRepository#enable($id)") {
if (DEBUG) {
@@ -303,8 +294,12 @@ constructor(
private interface DisplayConnectionListener : DisplayListener {
override fun onDisplayConnected(id: Int) {}
+
override fun onDisplayDisconnected(id: Int) {}
+
override fun onDisplayAdded(id: Int) {}
+
override fun onDisplayRemoved(id: Int) {}
+
override fun onDisplayChanged(id: Int) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index d19efbdd8026..87b0f0177d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -19,8 +19,12 @@ import android.content.Context
import android.os.Bundle
import android.view.View
import android.widget.TextView
+import androidx.core.view.updatePadding
+import com.android.systemui.biometrics.Utils
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIBottomSheetDialog
+import com.android.systemui.statusbar.policy.ConfigurationController
+import kotlin.math.max
/**
* Dialog used to decide what to do with a connected display.
@@ -32,8 +36,9 @@ class MirroringConfirmationDialog(
context: Context,
private val onStartMirroringClickListener: View.OnClickListener,
private val onCancelMirroring: View.OnClickListener,
+ configurationController: ConfigurationController? = null,
theme: Int = R.style.Theme_SystemUI_Dialog,
-) : SystemUIBottomSheetDialog(context, theme) {
+) : SystemUIBottomSheetDialog(context, configurationController, theme) {
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
@@ -56,5 +61,23 @@ class MirroringConfirmationDialog(
onCancelMirroring.onClick(null)
}
}
+ setupInsets()
+ }
+
+ private fun setupInsets() {
+ // This avoids overlap between dialog content and navigation bars.
+ requireViewById<View>(R.id.cd_bottom_sheet).apply {
+ val navbarInsets = Utils.getNavbarInsets(context)
+ val defaultDialogBottomInset =
+ context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
+ // we only care about the bottom inset as in all other configuration where navigations
+ // are in other display sides there is no overlap with the dialog.
+ updatePadding(bottom = max(navbarInsets.bottom, defaultDialogBottomInset))
+ }
+ }
+
+ override fun onConfigurationChanged() {
+ super.onConfigurationChanged()
+ setupInsets()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 86ef439361b0..91f535df586a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor
import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay
import com.android.systemui.display.ui.view.MirroringConfirmationDialog
+import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -41,7 +42,8 @@ constructor(
private val context: Context,
private val connectedDisplayInteractor: ConnectedDisplayInteractor,
@Application private val scope: CoroutineScope,
- @Background private val bgDispatcher: CoroutineDispatcher
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val configurationController: ConfigurationController
) {
private var dialog: Dialog? = null
@@ -71,7 +73,8 @@ constructor(
onCancelMirroring = {
scope.launch(bgDispatcher) { pendingDisplay.ignore() }
hideDialog()
- }
+ },
+ configurationController
)
.apply { show() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
index 83c239f169ef..dd5860484a55 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -17,15 +17,19 @@
package com.android.systemui.flags
import android.util.Log
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.ConditionalRestarter.Condition
+import com.android.systemui.util.kotlin.UnflaggedApplication
+import com.android.systemui.util.kotlin.UnflaggedBackground
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Named
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
/** Restarts the process after all passed in [Condition]s are true. */
@@ -35,11 +39,10 @@ constructor(
private val systemExitRestarter: SystemExitRestarter,
private val conditions: Set<@JvmSuppressWildcards Condition>,
@Named(RESTART_DELAY) private val restartDelaySec: Long,
- @Application private val applicationScope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @UnflaggedApplication private val applicationScope: CoroutineScope,
+ @UnflaggedBackground private val backgroundDispatcher: CoroutineContext,
) : Restarter {
- private var restartJob: Job? = null
private var pendingReason = ""
private var androidRestartRequested = false
@@ -57,17 +60,19 @@ constructor(
private fun scheduleRestart(reason: String = "") {
pendingReason = if (reason.isEmpty()) pendingReason else reason
- if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
- if (restartJob == null) {
- restartJob =
- applicationScope.launch(backgroundDispatcher) {
+ applicationScope.launch(backgroundDispatcher) {
+ combine(conditions.map { condition -> condition.canRestartNow }) { it.all { it } }
+ // Once all conditions are met, delay.
+ .transformLatest { allConditionsMet ->
+ if (allConditionsMet) {
delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
- restartNow()
+ emit(Unit)
}
- }
- } else {
- restartJob?.cancel()
- restartJob = null
+ }
+ // Once we have successfully delayed _once_, continue to restart.
+ .first()
+
+ restartNow()
}
}
@@ -94,7 +99,7 @@ constructor(
* multiple [Condition]s are being checked. If any one [Condition] returns false, all the
* [Condition]s will need to be rechecked on the next restart attempt.
*/
- fun canRestartNow(retryFn: () -> Unit): Boolean
+ val canRestartNow: Flow<Boolean>
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index ae0c8da73c40..8c81fbbaeb9d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -118,7 +118,7 @@ object Flags {
// TODO(b/292213543): Tracking Bug
@JvmField
val NOTIFICATION_GROUP_EXPANSION_CHANGE =
- unreleasedFlag("notification_group_expansion_change", teamfood = true)
+ releasedFlag("notification_group_expansion_change")
// TODO(b/301955929)
@JvmField
@@ -263,7 +263,7 @@ object Flags {
// TODO(b/290652751): Tracking bug.
@JvmField
val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
- releasedFlag("migrate_split_keyguard_bottom_area")
+ unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true)
// TODO(b/297037052): Tracking bug.
@JvmField
@@ -278,7 +278,7 @@ object Flags {
/** Migrate the lock icon view to the new keyguard root view. */
// TODO(b/286552209): Tracking bug.
- @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon")
+ @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true)
// TODO(b/288276738): Tracking bug.
@JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
@@ -549,12 +549,6 @@ object Flags {
val LOCKSCREEN_ENABLE_LANDSCAPE =
unreleasedFlag("lockscreen.enable_landscape")
- // TODO(b/273443374): Tracking Bug
- @Keep
- @JvmField
- val LOCKSCREEN_LIVE_WALLPAPER =
- sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true)
-
// TODO(b/281648899): Tracking bug
@Keep
@JvmField
@@ -776,7 +770,7 @@ object Flags {
val ENABLE_NEW_PRIVACY_DIALOG = releasedFlag("enable_new_privacy_dialog")
// TODO(b/289573946): Tracking Bug
- @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text", teamfood = true)
+ @JvmField val PRECOMPUTED_TEXT = releasedFlag("precomputed_text")
// TODO(b/302087895): Tracking Bug
@JvmField val CALL_LAYOUT_ASYNC_SET_DATA = unreleasedFlag("call_layout_async_set_data")
diff --git a/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt
new file mode 100644
index 000000000000..f5b30cf4b54f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/NotOccludedCondition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.flags
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class NotOccludedCondition
+@Inject
+constructor(
+ private val keyguardTransitionInteractorLazy: Lazy<KeyguardTransitionInteractor>,
+) : ConditionalRestarter.Condition {
+
+ override val canRestartNow: Flow<Boolean>
+ get() {
+ return keyguardTransitionInteractorLazy
+ .get()
+ .transitionValue(KeyguardState.OCCLUDED)
+ .map { it == 0f }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
index 3120638cb17f..dc08570447a5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -16,34 +16,34 @@
package com.android.systemui.flags
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.statusbar.policy.BatteryController
+import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
/** Returns true when the device is plugged in. */
class PluggedInCondition
@Inject
constructor(
- private val batteryController: BatteryController,
+ private val batteryControllerLazy: Lazy<BatteryController>,
) : ConditionalRestarter.Condition {
- var listenersAdded = false
- var retryFn: (() -> Unit)? = null
-
- val batteryCallback =
- object : BatteryController.BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- retryFn?.invoke()
+ override val canRestartNow = conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(pluggedIn)
+ }
}
- }
-
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- if (!listenersAdded) {
- listenersAdded = true
- batteryController.addCallback(batteryCallback)
- }
+ batteryControllerLazy.get().addCallback(batteryCallback)
- this.retryFn = retryFn
+ trySend(batteryControllerLazy.get().isPluggedIn)
- return batteryController.isPluggedIn
+ awaitClose { batteryControllerLazy.get().removeCallback(batteryCallback) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
index 7ccc26c063d3..4a5cc641dcf1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlag.kt
@@ -16,7 +16,6 @@
package com.android.systemui.flags
-import android.util.Log
import com.android.systemui.Dependency
/**
@@ -65,8 +64,7 @@ private constructor(
* }
* ````
*/
- fun assertInLegacyMode() =
- check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+ fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, flagName)
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -81,13 +79,8 @@ private constructor(
* }
* ```
*/
- fun isUnexpectedlyInLegacyMode(): Boolean {
- if (!isEnabled) {
- val message = "New code path expects $flagName to be enabled."
- Log.wtf(TAG, message, Exception(message))
- }
- return !isEnabled
- }
+ fun isUnexpectedlyInLegacyMode(): Boolean =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, flagName)
companion object {
private const val TAG = "RefactorFlag"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
new file mode 100644
index 000000000000..2aa397f3e744
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/RefactorFlagUtils.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util.Log
+
+/**
+ * Utilities for writing your own objects to uphold refactor flag conventions.
+ *
+ * Example usage:
+ * ```
+ * object SomeRefactor {
+ * const val FLAG_NAME = Flags.SOME_REFACTOR
+ * @JvmStatic inline val isEnabled get() = Flags.someRefactor()
+ * @JvmStatic inline fun isUnexpectedlyInLegacyMode() =
+ * RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ * @JvmStatic inline fun assertInLegacyMode() =
+ * RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ * }
+ * ```
+ */
+@Suppress("NOTHING_TO_INLINE")
+object RefactorFlagUtils {
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ *
+ * Example usage:
+ * ```
+ * public void setNewController(SomeController someController) {
+ * if (SomeRefactor.isUnexpectedlyInLegacyMode()) return;
+ * mSomeController = someController;
+ * }
+ * ```
+ */
+ inline fun isUnexpectedlyInLegacyMode(isEnabled: Boolean, flagName: Any): Boolean {
+ val inLegacyMode = !isEnabled
+ if (inLegacyMode) {
+ val message = "New code path expects $flagName to be enabled."
+ Log.wtf("RefactorFlag", message, IllegalStateException(message))
+ }
+ return inLegacyMode
+ }
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ *
+ * Example usage:
+ * ```
+ * public void setSomeLegacyController(SomeController someController) {
+ * SomeRefactor.assertInLegacyMode();
+ * mSomeController = someController;
+ * }
+ * ````
+ */
+ inline fun assertInLegacyMode(isEnabled: Boolean, flagName: Any) =
+ check(!isEnabled) { "Legacy code path not supported when $flagName is enabled." }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
index 49e61afbdcd6..3c9bc368e852 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -16,34 +16,19 @@
package com.android.systemui.flags
-import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
class ScreenIdleCondition
@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
-) : ConditionalRestarter.Condition {
+constructor(private val powerInteractorLazy: Lazy<PowerInteractor>) :
+ ConditionalRestarter.Condition {
- var listenersAdded = false
- var retryFn: (() -> Unit)? = null
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- retryFn?.invoke()
- }
- }
-
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- if (!listenersAdded) {
- listenersAdded = true
- wakefulnessLifecycle.addObserver(observer)
+ override val canRestartNow: Flow<Boolean>
+ get() {
+ return powerInteractorLazy.get().isAsleep
}
-
- this.retryFn = retryFn
-
- return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index bc0713952ce1..6f491d88dab4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -36,9 +36,9 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
+import com.android.systemui.util.TraceUtils.Companion.runBlocking
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
class CustomizationProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@@ -132,7 +132,7 @@ class CustomizationProvider :
throw UnsupportedOperationException()
}
- return runBlocking(mainDispatcher) { insertSelection(values) }
+ return runBlocking("$TAG#insert", mainDispatcher) { insertSelection(values) }
}
override fun query(
@@ -142,7 +142,7 @@ class CustomizationProvider :
selectionArgs: Array<out String>?,
sortOrder: String?,
): Cursor? {
- return runBlocking(mainDispatcher) {
+ return runBlocking("$TAG#query", mainDispatcher) {
when (uriMatcher.match(uri)) {
MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
MATCH_CODE_ALL_SLOTS -> querySlots()
@@ -172,7 +172,7 @@ class CustomizationProvider :
throw UnsupportedOperationException()
}
- return runBlocking(mainDispatcher) { deleteSelection(uri, selectionArgs) }
+ return runBlocking("$TAG#delete", mainDispatcher) { deleteSelection(uri, selectionArgs) }
}
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 47798955b43d..2b1cdc2ff026 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -202,7 +202,7 @@ public class KeyguardService extends Service {
// Wrap Keyguard going away animation.
// Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
public static IRemoteTransition wrap(final KeyguardViewMediator keyguardViewMediator,
- final IRemoteAnimationRunner runner, final boolean lockscreenLiveWallpaperEnabled) {
+ final IRemoteAnimationRunner runner) {
return new IRemoteTransition.Stub() {
@GuardedBy("mLeashMap")
@@ -236,9 +236,8 @@ public class KeyguardService extends Service {
}
}
initAlphaForAnimationTargets(t, apps);
- if (lockscreenLiveWallpaperEnabled) {
- initAlphaForAnimationTargets(t, wallpapers);
- }
+ initAlphaForAnimationTargets(t, wallpapers);
+
t.apply();
runner.onAnimationStart(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 0bac40bcbcc1..c8c06ae65d45 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -707,8 +707,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
return@postDelayed
}
- if ((wallpaperTargets?.isNotEmpty() == true) &&
- wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+ if ((wallpaperTargets?.isNotEmpty() == true)) {
fadeInWallpaper()
hideKeyguardViewAfterRemoteAnimation()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index e893c6305dee..4e6a872cb3f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -184,8 +184,6 @@ import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-
-
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1517,12 +1515,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
setShowingLocked(false /* showing */, true /* forceCallbacks */);
}
- boolean isLLwpEnabled = getWallpaperManager().isLockscreenLiveWallpaperEnabled();
mKeyguardTransitions.register(
- KeyguardService.wrap(this, getExitAnimationRunner(), isLLwpEnabled),
- KeyguardService.wrap(this, getOccludeAnimationRunner(), isLLwpEnabled),
- KeyguardService.wrap(this, getOccludeByDreamAnimationRunner(), isLLwpEnabled),
- KeyguardService.wrap(this, getUnoccludeAnimationRunner(), isLLwpEnabled));
+ KeyguardService.wrap(this, getExitAnimationRunner()),
+ KeyguardService.wrap(this, getOccludeAnimationRunner()),
+ KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()),
+ KeyguardService.wrap(this, getUnoccludeAnimationRunner()));
final ContentResolver cr = mContext.getContentResolver();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 3cdff76881ee..6a0d5954fc44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -56,6 +56,10 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -78,10 +82,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
/**
* API to run face authentication and detection for device entry / on keyguard (as opposed to the
@@ -368,10 +368,12 @@ constructor(
return arrayOf(
Pair(
and(
- displayStateInteractor.isDefaultDisplayOff,
- keyguardTransitionInteractor.isFinishedInStateWhere(
- KeyguardState::deviceIsAwakeInState),
- ).isFalse(),
+ displayStateInteractor.isDefaultDisplayOff,
+ keyguardTransitionInteractor.isFinishedInStateWhere(
+ KeyguardState::deviceIsAwakeInState
+ ),
+ )
+ .isFalse(),
// this can happen if an app is requesting for screen off, the display can
// turn off without wakefulness.isStartingToSleepOrAsleep calls
"displayIsNotOffWhileFullyTransitionedToAwake",
@@ -381,10 +383,7 @@ constructor(
"isFaceAuthEnrolledAndEnabled"
),
Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
- Pair(
- powerInteractor.isAsleep.isFalse(),
- "deviceNotAsleep"
- ),
+ Pair(powerInteractor.isAsleep.isFalse(), "deviceNotAsleep"),
Pair(
keyguardInteractor.isSecureCameraActive
.isFalse()
@@ -430,10 +429,15 @@ constructor(
private val faceAuthCallback =
object : FaceManager.AuthenticationCallback() {
override fun onAuthenticationFailed() {
- _authenticationStatus.value = FailedFaceAuthenticationStatus()
_isAuthenticated.value = false
faceAuthLogger.authenticationFailed()
- onFaceAuthRequestCompleted()
+ if (!_isLockedOut.value) {
+ // onAuthenticationError gets invoked before onAuthenticationFailed when the
+ // last auth attempt locks out face authentication.
+ // Skip updating the authentication status in such a scenario.
+ _authenticationStatus.value = FailedFaceAuthenticationStatus()
+ onFaceAuthRequestCompleted()
+ }
}
override fun onAuthenticationAcquired(acquireInfo: Int) {
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 84cd3ef622ba..3eef6aa37122 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
@@ -25,6 +25,7 @@ import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import java.util.UUID
@@ -48,8 +49,8 @@ import kotlinx.coroutines.flow.filter
* [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
* this repository.
*
- * To print all transitions to logcat to help with debugging, run this command:
- * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ * To print all transitions to logcat to help with debugging, run this command: adb shell settings
+ * put global systemui/buffer/KeyguardLog VERBOSE
*
* This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
*/
@@ -73,11 +74,8 @@ interface KeyguardTransitionRepository {
/**
* Begin a transition from one state to another. Transitions are interruptible, and will issue a
* [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
- *
- * When canceled, there are two options: to continue from the current position of the prior
- * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
*/
- fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
+ fun startTransition(info: TransitionInfo): UUID?
/**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -138,10 +136,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
)
}
- override fun startTransition(
- info: TransitionInfo,
- resetIfCanceled: Boolean,
- ): UUID? {
+ override fun startTransition(info: TransitionInfo): UUID? {
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return null
@@ -149,10 +144,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
val startingValue =
if (lastStep.transitionState != TransitionState.FINISHED) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
- if (resetIfCanceled) {
- 0f
- } else {
- lastStep.value
+ when (info.modeOnCanceled) {
+ TransitionModeOnCanceled.LAST_VALUE -> lastStep.value
+ TransitionModeOnCanceled.RESET -> 0f
+ TransitionModeOnCanceled.REVERSE -> 1f - lastStep.value
}
} else {
0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index d06f31fed8db..7e360cfad66d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -26,13 +26,13 @@ import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
@SysUISingleton
class FromAlternateBouncerTransitionInteractor
@@ -130,11 +130,16 @@ constructor(
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
- duration = TRANSITION_DURATION_MS.inWholeMilliseconds
+ duration =
+ when (toState) {
+ KeyguardState.GONE -> TO_GONE_DURATION
+ else -> TRANSITION_DURATION_MS
+ }.inWholeMilliseconds
}
}
companion object {
val TRANSITION_DURATION_MS = 300.milliseconds
+ val TO_GONE_DURATION = 500.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 6e0aa4c7682d..a331a668e135 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -24,6 +24,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -65,8 +66,21 @@ constructor(
)
.collect { (_, lastStartedStep, occluded) ->
if (lastStartedStep.to == KeyguardState.AOD) {
- startTransitionTo(
+ val toState =
if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ val modeOnCanceled =
+ if (
+ toState == KeyguardState.LOCKSCREEN &&
+ lastStartedStep.from == KeyguardState.LOCKSCREEN
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+
+ startTransitionTo(
+ toState = toState,
+ modeOnCanceled = modeOnCanceled,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index c67153a5963d..eace0c70cb5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
@@ -114,8 +115,9 @@ constructor(
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
- resetIfCancelled = true,
+ toState =
+ if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index c39a4c9c4172..95ac0d8a2999 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -27,9 +27,11 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.TraceUtils.Companion.launch
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
@@ -42,7 +44,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.launch
@SysUISingleton
class FromLockscreenTransitionInteractor
@@ -135,7 +136,7 @@ constructor(
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToDreaming") {
keyguardInteractor.isAbleToDream
.sample(
combine(
@@ -168,7 +169,7 @@ constructor(
}
private fun listenForLockscreenToPrimaryBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") {
keyguardInteractor.primaryBouncerShowing
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -183,7 +184,7 @@ constructor(
}
private fun listenForLockscreenToAlternateBouncer() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAlternateBouncer") {
keyguardInteractor.alternateBouncerShowing
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -201,7 +202,7 @@ constructor(
/* Starts transitions when manually dragging up the bouncer from the lockscreen. */
private fun listenForLockscreenToPrimaryBouncerDragging() {
var transitionId: UUID? = null
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
shadeRepository.shadeModel
.sample(
combine(
@@ -286,7 +287,7 @@ constructor(
return
}
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGone") {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -303,7 +304,7 @@ constructor(
return
}
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToGoneDragging") {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
@@ -316,7 +317,7 @@ constructor(
}
private fun listenForLockscreenToOccluded() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToOccluded") {
keyguardInteractor.isKeyguardOccluded
.sample(transitionInteractor.startedKeyguardState, ::Pair)
.collect { (isOccluded, keyguardState) ->
@@ -328,7 +329,7 @@ constructor(
}
private fun listenForLockscreenToAodOrDozing() {
- scope.launch {
+ scope.launch("$TAG#listenForLockscreenToAodOrDozing") {
powerInteractor.isAsleep
.sample(
combine(
@@ -340,8 +341,20 @@ constructor(
)
.collect { (isAsleep, lastStartedStep, isAodAvailable) ->
if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
- startTransitionTo(
+ val toState =
if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
+ val modeOnCanceled =
+ if (
+ toState == KeyguardState.AOD &&
+ lastStartedStep.from == KeyguardState.AOD
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ startTransitionTo(
+ toState = toState,
+ modeOnCanceled = modeOnCanceled,
)
}
}
@@ -362,6 +375,7 @@ constructor(
}
companion object {
+ const val TAG = "FromLockscreenTransitionInteractor"
private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index f1649fb2851e..24b666185ce2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -25,6 +25,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
@@ -236,7 +237,7 @@ constructor(
getDefaultAnimatorForTransitionsToState(KeyguardState.GONE).apply {
this.duration = duration.inWholeMilliseconds
},
- resetIfCancelled = true
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
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 de791aa23e22..fe9370fd8ce9 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
@@ -23,7 +23,6 @@ import android.content.Context
import android.content.Intent
import android.util.Log
import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.res.R
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
@@ -44,11 +43,12 @@ import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentati
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.TraceUtils.Companion.traceAsync
+import com.android.systemui.util.TraceUtils.Companion.withContext
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -59,7 +59,6 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@@ -417,10 +416,8 @@ constructor(
}
private suspend fun isFeatureDisabledByDevicePolicy(): Boolean =
- traceAsync(TAG, "isFeatureDisabledByDevicePolicy") {
- withContext(backgroundDispatcher) {
- devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
- }
+ withContext("$TAG#isFeatureDisabledByDevicePolicy", backgroundDispatcher) {
+ devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId)
}
companion object {
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 fbe26de4e9ba..b0b857729c77 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
@@ -143,6 +143,11 @@ constructor(
val dozingToLockscreenTransition: Flow<TransitionStep> =
repository.transition(DOZING, LOCKSCREEN)
+ /** Receive all [TransitionStep] matching a filter of [from]->[to] */
+ fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
+ return repository.transition(from, to)
+ }
+
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
* Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 54c6d5f6d09f..76018080848f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -21,6 +21,7 @@ import android.util.Log
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
@@ -49,7 +50,7 @@ sealed class TransitionInteractor(
fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
- resetIfCancelled: Boolean = false,
+ modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
): UUID? {
if (
fromState != transitionInteractor.startedKeyguardState.value &&
@@ -73,8 +74,8 @@ sealed class TransitionInteractor(
fromState,
toState,
animator,
- ),
- resetIfCancelled
+ modeOnCanceled,
+ )
)
}
@@ -91,8 +92,8 @@ sealed class TransitionInteractor(
// so use the last finishedKeyguardState to determine the overriding FROM state
if (finishedKeyguardState == fromState) {
startTransitionTo(
- KeyguardState.OCCLUDED,
- resetIfCancelled = true,
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
index bfccf3fe076c..7a37365a9cbe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionInfo.kt
@@ -22,7 +22,13 @@ data class TransitionInfo(
val ownerName: String,
val from: KeyguardState,
val to: KeyguardState,
- val animator: ValueAnimator?, // 'null' animator signal manual control
+ /** [null] animator signals manual control, otherwise transition run by the animator */
+ val animator: ValueAnimator?,
+ /**
+ * If the transition resets in the cancellation of another transition, use this mode to
+ * determine how to continue.
+ */
+ val modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
) {
override fun toString(): String =
"TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
new file mode 100644
index 000000000000..56f90bdb4f71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionModeOnCanceled.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** When canceled, provide different ways to start the next transition. */
+enum class TransitionModeOnCanceled {
+ /** Proceed from the last value. If canceled at .7, start from .7 and end at 1 */
+ LAST_VALUE,
+ /** Start over from 0. If canceled at .7, start from 0 and end at 1 */
+ RESET,
+ /** Reverse the transition. If canceled at .7, start from 1-.7 (0.3) and end at 1 */
+ REVERSE
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
index 342a440d972b..9371d4e2d465 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -50,8 +50,11 @@ constructor(
override fun addViews(constraintLayout: ConstraintLayout) {
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
- LayoutInflater.from(constraintLayout.context)
- .inflate(R.layout.ambient_indication, constraintLayout, true)
+ val view =
+ LayoutInflater.from(constraintLayout.context)
+ .inflate(R.layout.ambient_indication, constraintLayout, false)
+
+ constraintLayout.addView(view)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
new file mode 100644
index 000000000000..023d16cab013
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down ALTERNATE_BOUNCER->GONE transition into discrete steps for corresponding views to
+ * consume.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class AlternateBouncerToGoneTransitionViewModel
+@Inject
+constructor(
+ bouncerToGoneFlows: BouncerToGoneFlows,
+) {
+
+ /** Scrim alpha values */
+ val scrimAlpha: Flow<ScrimAlpha> =
+ bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, ALTERNATE_BOUNCER)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
new file mode 100644
index 000000000000..da74f2fa061e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import dagger.Lazy
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/** ALTERNATE and PRIMARY bouncers common animations */
+@OptIn(ExperimentalCoroutinesApi::class)
+class BouncerToGoneFlows
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
+ private val featureFlags: FeatureFlagsClassic,
+ private val shadeInteractor: ShadeInteractor,
+) {
+ /** Common fade for scrim alpha values during *BOUNCER->GONE */
+ fun scrimAlpha(duration: Duration, fromState: KeyguardState): Flow<ScrimAlpha> {
+ return if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+ keyguardDismissActionInteractor
+ .get()
+ .willAnimateDismissActionOnLockscreen
+ .flatMapLatest { createScrimAlphaFlow(duration, fromState) { it } }
+ } else {
+ createScrimAlphaFlow(
+ duration,
+ fromState,
+ primaryBouncerInteractor::willRunDismissFromKeyguard
+ )
+ }
+ }
+
+ private fun createScrimAlphaFlow(
+ duration: Duration,
+ fromState: KeyguardState,
+ willRunAnimationOnKeyguard: () -> Boolean
+ ): Flow<ScrimAlpha> {
+ var isShadeExpanded = false
+ var leaveShadeOpen: Boolean = false
+ var willRunDismissFromKeyguard: Boolean = false
+ val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = duration,
+ transitionFlow = interactor.transition(fromState, GONE)
+ )
+
+ return shadeInteractor.shadeExpansion.flatMapLatest { shadeExpansion ->
+ transitionAnimation
+ .createFlow(
+ duration = duration,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
+ isShadeExpanded = shadeExpansion > 0f
+ },
+ onStep = { 1f - it },
+ )
+ .map {
+ if (willRunDismissFromKeyguard) {
+ if (isShadeExpanded) {
+ ScrimAlpha(
+ behindAlpha = it,
+ notificationsAlpha = it,
+ )
+ } else {
+ ScrimAlpha()
+ }
+ } else if (leaveShadeOpen) {
+ ScrimAlpha(
+ behindAlpha = 1f,
+ notificationsAlpha = 1f,
+ )
+ } else {
+ ScrimAlpha(behindAlpha = it)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 078318196883..0e95be20d059 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -16,7 +16,6 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
@@ -24,6 +23,8 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -33,7 +34,6 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.map
/**
* Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
@@ -49,11 +49,12 @@ constructor(
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
featureFlags: FeatureFlagsClassic,
+ bouncerToGoneFlows: BouncerToGoneFlows,
) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
transitionDuration = TO_GONE_DURATION,
- transitionFlow = interactor.primaryBouncerToGoneTransition,
+ transitionFlow = interactor.transition(PRIMARY_BOUNCER, GONE)
)
private var leaveShadeOpen: Boolean = false
@@ -110,38 +111,6 @@ constructor(
)
}
- /** Scrim alpha values */
val scrimAlpha: Flow<ScrimAlpha> =
- if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
- keyguardDismissActionInteractor
- .get()
- .willAnimateDismissActionOnLockscreen
- .flatMapLatest { createScrimAlphaFlow { it } }
- } else {
- createScrimAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
- }
- private fun createScrimAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<ScrimAlpha> {
- return transitionAnimation
- .createFlow(
- duration = TO_GONE_DURATION,
- interpolator = EMPHASIZED_ACCELERATE,
- onStart = {
- leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
- willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
- },
- onStep = { 1f - it },
- )
- .map {
- if (willRunDismissFromKeyguard) {
- ScrimAlpha()
- } else if (leaveShadeOpen) {
- ScrimAlpha(
- behindAlpha = 1f,
- notificationsAlpha = 1f,
- )
- } else {
- ScrimAlpha(behindAlpha = it)
- }
- }
- }
+ bouncerToGoneFlows.scrimAlpha(TO_GONE_DURATION, PRIMARY_BOUNCER)
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 67531ad9926a..fd6b3f1e0f45 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -190,17 +190,6 @@ public class LogModule {
}
}
- /**
- * Provides a logging buffer for all logs related to Quick Settings tiles. This LogBuffer is
- * unique for each tile.
- * go/qs-tile-refactor
- */
- @Provides
- @QSTilesDefaultLog
- public static LogBuffer provideQuickSettingsTilesLogBuffer(LogBufferFactory factory) {
- return factory.create("QSTileLog", 25 /* maxSize */, false /* systrace */);
- }
-
@Provides
@QSTilesLogBuffers
public static Map<TileSpec, LogBuffer> provideQuickSettingsTilesLogBufferCache() {
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
index 565bf241a194..baa07c14ae04 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/Diffable.kt
@@ -95,7 +95,7 @@ fun Flow<Boolean>.logDiffsForTable(
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Boolean ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Boolean, newVal: Boolean ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -114,7 +114,7 @@ fun Flow<Int>.logDiffsForTable(
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Int, newVal: Int ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -133,7 +133,7 @@ fun Flow<Int?>.logDiffsForTable(
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: Int? ->
+ return this.pairwiseBy(initialValueFun) { prevVal: Int?, newVal: Int? ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -152,7 +152,7 @@ fun Flow<String?>.logDiffsForTable(
tableLogBuffer.logChange(columnPrefix, columnName, initialValue, isInitial = true)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: String? ->
+ return this.pairwiseBy(initialValueFun) { prevVal: String?, newVal: String? ->
if (prevVal != newVal) {
tableLogBuffer.logChange(columnPrefix, columnName, newVal)
}
@@ -176,7 +176,7 @@ fun <T> Flow<List<T>>.logDiffsForTable(
)
initialValue
}
- return this.pairwiseBy(initialValueFun) { prevVal, newVal: List<T> ->
+ return this.pairwiseBy(initialValueFun) { prevVal: List<T>, newVal: List<T> ->
if (prevVal != newVal) {
// TODO(b/267761156): Can we log list changes without using toString?
tableLogBuffer.logChange(columnPrefix, columnName, newVal.toString())
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index a48e56a9f158..7cb5b3bc0924 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -731,6 +731,7 @@ constructor(
removePlayer(existingSmartspaceMediaKey, dismissMediaData = false)
removedPlayer?.run {
debugLogger.logPotentialMemoryLeak(existingSmartspaceMediaKey)
+ onDestroy()
}
}
@@ -1302,6 +1303,7 @@ internal object MediaPlayerData {
val removedPlayer = removeMediaPlayer(key)
if (removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
+ removedPlayer.onDestroy()
}
val sortKey =
MediaSortKey(
@@ -1329,6 +1331,7 @@ internal object MediaPlayerData {
val removedPlayer = removeMediaPlayer(key)
if (!update && removedPlayer != null && removedPlayer != player) {
debugLogger?.logPotentialMemoryLeak(key)
+ removedPlayer.onDestroy()
}
val sortKey =
MediaSortKey(
@@ -1357,7 +1360,10 @@ internal object MediaPlayerData {
// MediaPlayer should not be visible
// no need to set isDismissed flag.
val removedPlayer = removeMediaPlayer(newKey)
- removedPlayer?.run { debugLogger?.logPotentialMemoryLeak(newKey) }
+ removedPlayer?.run {
+ debugLogger?.logPotentialMemoryLeak(newKey)
+ onDestroy()
+ }
mediaData.put(newKey, it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index a53f0f11c380..19621199aad5 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -23,7 +23,6 @@ import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGE
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
-import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@@ -35,16 +34,48 @@ import javax.inject.Inject
class MediaProjectionMetricsLogger
@Inject
constructor(private val service: IMediaProjectionManager) {
+
/**
* Request to log that the permission was requested.
*
+ * @param hostUid The UID of the package that initiates MediaProjection.
* @param sessionCreationSource The entry point requesting permission to capture.
*/
- fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) {
- notifyToServer(
- MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
- sessionCreationSource
- )
+ fun notifyProjectionInitiated(hostUid: Int, sessionCreationSource: SessionCreationSource) {
+ try {
+ service.notifyPermissionRequestInitiated(
+ hostUid,
+ sessionCreationSource.toMetricsConstant()
+ )
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error notifying server of projection initiated", e)
+ }
+ }
+
+ /**
+ * Request to log that the permission request was displayed.
+ *
+ * @param hostUid The UID of the package that initiates MediaProjection.
+ */
+ fun notifyPermissionRequestDisplayed(hostUid: Int) {
+ try {
+ service.notifyPermissionRequestDisplayed(hostUid)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error notifying server of projection displayed", e)
+ }
+ }
+
+ /**
+ * Request to log that the app selector was displayed.
+ *
+ * @param hostUid The UID of the package that initiates MediaProjection.
+ */
+ fun notifyAppSelectorDisplayed(hostUid: Int) {
+ try {
+ service.notifyAppSelectorDisplayed(hostUid)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error notifying server of app selector displayed", e)
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index 0bbcfd9de24c..04d55665f572 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -92,6 +92,7 @@ class MediaProjectionAppSelectorActivity(
component =
componentFactory.create(
hostUserHandle = hostUserHandle,
+ hostUid = hostUid,
callingPackage = callingPackage,
view = this,
resultHandler = this,
@@ -305,6 +306,17 @@ class MediaProjectionAppSelectorActivity(
)
}
+ private val hostUid: Int
+ get() {
+ if (!intent.hasExtra(EXTRA_HOST_APP_UID)) {
+ error(
+ "MediaProjectionAppSelectorActivity should be provided with " +
+ "$EXTRA_HOST_APP_UID extra"
+ )
+ }
+ return intent.getIntExtra(EXTRA_HOST_APP_UID, /* defaultValue= */ -1)
+ }
+
companion object {
const val TAG = "MediaProjectionAppSelectorActivity"
@@ -315,8 +327,16 @@ class MediaProjectionAppSelectorActivity(
*/
const val EXTRA_CAPTURE_REGION_RESULT_RECEIVER = "capture_region_result_receiver"
- /** UID of the app that originally launched the media projection flow (host app user) */
+ /**
+ * User on the device that launched the media projection flow. (Primary, Secondary, Guest,
+ * Work, etc)
+ */
const val EXTRA_HOST_APP_USER_HANDLE = "launched_from_user_handle"
+ /**
+ * The kernel user-ID that has been assigned to the app that originally launched the media
+ * projection flow.
+ */
+ const val EXTRA_HOST_APP_UID = "launched_from_host_uid"
const val KEY_CAPTURE_TARGET = "capture_region"
/** Set up intent for the [ChooserActivity] */
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 8c6f307c84d6..d2471225a093 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -57,6 +57,8 @@ import kotlinx.coroutines.SupervisorJob
@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUserHandle
+@Qualifier @Retention(AnnotationRetention.BINARY) annotation class HostUid
+
@Retention(AnnotationRetention.RUNTIME) @Scope annotation class MediaProjectionAppSelectorScope
@Module(
@@ -143,6 +145,7 @@ interface MediaProjectionAppSelectorComponent {
/** Create a factory to inject the activity into the graph */
fun create(
@BindsInstance @HostUserHandle hostUserHandle: UserHandle,
+ @BindsInstance @HostUid hostUid: Int,
@BindsInstance @MediaProjectionAppSelector callingPackage: String?,
@BindsInstance view: MediaProjectionAppSelectorView,
@BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 69132d3662d4..67ef119a7428 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -47,13 +47,14 @@ constructor(
private val thumbnailLoader: RecentTaskThumbnailLoader,
@MediaProjectionAppSelector private val isFirstStart: Boolean,
private val logger: MediaProjectionMetricsLogger,
+ @HostUid private val hostUid: Int,
) {
fun init() {
// Only log during the first start of the app selector.
// Don't log when the app selector restarts due to a config change.
if (isFirstStart) {
- logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ logger.notifyAppSelectorDisplayed(hostUid)
}
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index fa418fc8b98b..f7cc589db068 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -140,7 +140,7 @@ public class MediaProjectionPermissionActivity extends Activity
if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
- SessionCreationSource.APP);
+ mUid, SessionCreationSource.APP);
}
final IMediaProjection projection =
MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
@@ -242,6 +242,7 @@ public class MediaProjectionPermissionActivity extends Activity
if (savedInstanceState == null) {
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ mUid,
appName == null
? SessionCreationSource.CAST
: SessionCreationSource.APP);
@@ -249,6 +250,10 @@ public class MediaProjectionPermissionActivity extends Activity
setUpDialog(mDialog);
mDialog.show();
+
+ if (savedInstanceState == null) {
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(mUid);
+ }
}
@Override
@@ -325,6 +330,9 @@ public class MediaProjectionPermissionActivity extends Activity
projection.asBinder());
intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
getHostUserHandle());
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID,
+ getLaunchedFromUid());
intent.putExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, mReviewGrantedConsentRequired);
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 051eeb0cfaf1..bd13d0686462 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -48,6 +48,7 @@ import android.widget.Switch;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -538,12 +539,17 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener,
Log.i(TAG, "Launching activity before click");
} else {
Log.i(TAG, "The activity is starting");
- ActivityLaunchAnimator.Controller controller = mViewClicked == null
- ? null
- : ActivityLaunchAnimator.Controller.fromView(mViewClicked, 0);
- mUiHandler.post(() ->
- mActivityStarter.startPendingIntentDismissingKeyguard(
- pendingIntent, null, controller)
+
+ ActivityLaunchAnimator.Controller controller =
+ mViewClicked == null ? null :
+ ActivityLaunchAnimator.Controller.fromView(
+ mViewClicked,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE
+ );
+ mActivityStarter.startPendingIntentMaybeDismissingKeyguard(
+ pendingIntent,
+ /* intentSentUiThreadCallback= */ null,
+ controller
);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 959afd86ee8d..f37f58db05f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -30,12 +30,12 @@ import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
@@ -45,7 +45,9 @@ import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -69,6 +71,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
private final DialogLaunchAnimator mDialogLaunchAnimator;
private final FeatureFlags mFlags;
private final PanelInteractor mPanelInteractor;
+ private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final UserContextProvider mUserContextProvider;
private long mMillisUntilFinished = 0;
@@ -88,7 +92,9 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
KeyguardDismissUtil keyguardDismissUtil,
KeyguardStateController keyguardStateController,
DialogLaunchAnimator dialogLaunchAnimator,
- PanelInteractor panelInteractor
+ PanelInteractor panelInteractor,
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+ UserContextProvider userContextProvider
) {
super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
@@ -99,6 +105,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
mKeyguardStateController = keyguardStateController;
mDialogLaunchAnimator = dialogLaunchAnimator;
mPanelInteractor = panelInteractor;
+ mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+ mUserContextProvider = userContextProvider;
}
@Override
@@ -190,6 +198,10 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
} else {
dialog.show();
}
+
+ int uid = mUserContextProvider.getUserContext().getUserId();
+ mMediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(uid);
+
return false;
};
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
index 1b0d5f9a9fdf..2f8fe42672c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -32,7 +32,6 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
@@ -43,6 +42,7 @@ import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -55,7 +55,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
private final KeyguardStateController mKeyguard;
protected IndividualSensorPrivacyController mSensorPrivacyController;
- private final SafetyCenterManager mSafetyCenterManager;
+ private final Boolean mIsSafetyCenterEnabled;
/**
* @return Id of the sensor that will be toggled
@@ -89,7 +89,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
statusBarStateController, activityStarter, qsLogger);
mSensorPrivacyController = sensorPrivacyController;
mKeyguard = keyguardStateController;
- mSafetyCenterManager = safetyCenterManager;
+ mIsSafetyCenterEnabled = safetyCenterManager.isSafetyCenterEnabled();
mSensorPrivacyController.observe(getLifecycle(), this);
}
@@ -138,7 +138,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
@Override
public Intent getLongClickIntent() {
- if (mSafetyCenterManager.isSafetyCenterEnabled()) {
+ if (mIsSafetyCenterEnabled) {
return new Intent(Settings.ACTION_PRIVACY_CONTROLS);
} else {
return new Intent(Settings.ACTION_PRIVACY_SETTINGS);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 9d100728e643..905d8effdadf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.base.actions
+import android.app.PendingIntent
import android.content.Intent
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
@@ -29,7 +30,7 @@ import javax.inject.Inject
* dismissing and tile from-view animations.
*/
@SysUISingleton
-class QSTileIntentUserActionHandler
+class QSTileIntentUserInputHandler
@Inject
constructor(private val activityStarter: ActivityStarter) {
@@ -43,4 +44,19 @@ constructor(private val activityStarter: ActivityStarter) {
}
activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
}
+
+ // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939
+ fun handle(view: View?, pendingIntent: PendingIntent) {
+ if (!pendingIntent.isActivity) {
+ return
+ }
+ val animationController: ActivityLaunchAnimator.Controller? =
+ view?.let {
+ ActivityLaunchAnimator.Controller.fromView(
+ it,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+ )
+ }
+ activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt
new file mode 100644
index 000000000000..4f25d3cde6c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DataUpdateTrigger.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.base.interactor
+
+/** Event that triggers data update */
+sealed interface DataUpdateTrigger {
+ /**
+ * State update is requested in a response to a user action.
+ * - [action] is the action that happened
+ * - [tileData] is the data state of the tile when that action took place
+ */
+ class UserInput<T>(val input: QSTileInput<T>) : DataUpdateTrigger
+
+ /** Force update current state. This is passed when the view needs a new state to show */
+ data object ForceUpdate : DataUpdateTrigger
+
+ /** The data is requested loaded for the first time */
+ data object InitialRequest : DataUpdateTrigger
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
index 7a22e3cf8bc8..a3e38500123e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.tiles.base.interactor
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -29,14 +28,20 @@ import kotlinx.coroutines.flow.Flow
interface QSTileDataInteractor<DATA_TYPE> {
/**
- * Returns the data to be mapped to [QSTileState]. Make sure to start the flow [Flow.onStart]
- * with the current state to update the tile as soon as possible.
+ * Returns a data flow scoped to the user. This means the subscription will live when the tile
+ * is listened for the [userId]. It's cancelled when the tile is not listened or the user
+ * changes.
+ *
+ * You can use [Flow.onStart] on the returned to update the tile with the current state as soon
+ * as possible.
*/
- fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<DATA_TYPE>
+ fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>
/**
- * Returns tile availability - whether this device currently supports this tile. Make sure to
- * start the flow [Flow.onStart] with the current state to update the tile as soon as possible.
+ * Returns tile availability - whether this device currently supports this tile.
+ *
+ * You can use [Flow.onStart] on the returned to update the tile with the current state as soon
+ * as possible.
*/
- fun availability(): Flow<Boolean>
+ fun availability(userId: Int): Flow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
index ffe38ddacfda..102fa3641ff4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt
@@ -16,12 +16,11 @@
package com.android.systemui.qs.tiles.base.interactor
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
-sealed interface StateUpdateTrigger {
- class UserAction<T>(val action: QSTileUserAction, val tileState: QSTileState, val tileData: T) :
- StateUpdateTrigger
- data object ForceUpdate : StateUpdateTrigger
- data object InitialRequest : StateUpdateTrigger
-}
+/** @see QSTileUserActionInteractor.handleInput */
+data class QSTileInput<T>(
+ val userId: Int,
+ val action: QSTileUserAction,
+ val data: T,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 14fc639c8aa8..09d7a1f7142d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -17,13 +17,14 @@
package com.android.systemui.qs.tiles.base.interactor
import android.annotation.WorkerThread
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
interface QSTileUserActionInteractor<DATA_TYPE> {
-
/**
- * Processes user input based on [userAction] and [currentData]. It's safe to run long running
- * computations inside this function in this.
+ * Processes user input based on [QSTileInput.userId], [QSTileInput.action], and
+ * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to
+ * [QSTileDataInteractor] to get [QSTileInput.data].
+ *
+ * It's safe to run long running computations inside this function in this.
*/
- @WorkerThread suspend fun handleInput(userAction: QSTileUserAction, currentData: DATA_TYPE)
+ @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 70a683b81f75..4dc1c82c5282 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -19,24 +19,23 @@ package com.android.systemui.qs.tiles.base.logging
import androidx.annotation.GuardedBy
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.dagger.QSTilesDefaultLog
import com.android.systemui.log.dagger.QSTilesLogBuffers
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.statusbar.StatusBarState
import javax.inject.Inject
-import javax.inject.Provider
@SysUISingleton
class QSTileLogger
@Inject
constructor(
@QSTilesLogBuffers logBuffers: Map<TileSpec, LogBuffer>,
- @QSTilesDefaultLog private val defaultLogBufferProvider: Provider<LogBuffer>,
+ private val factory: LogBufferFactory,
private val mStatusBarStateController: StatusBarStateController,
) {
@GuardedBy("logBufferCache") private val logBufferCache = logBuffers.toMutableMap()
@@ -129,10 +128,21 @@ constructor(
)
}
+ fun logForceUpdate(tileSpec: TileSpec) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data force update" })
+ }
+
+ fun logInitialRequest(tileSpec: TileSpec) {
+ tileSpec
+ .getLogBuffer()
+ .log(tileSpec.getLogTag(), LogLevel.DEBUG, {}, { "tile data initial update" })
+ }
+
/** Tracks state changes based on the data and trigger event. */
fun <T> logStateUpdate(
tileSpec: TileSpec,
- trigger: StateUpdateTrigger,
tileState: QSTileState,
data: T,
) {
@@ -142,11 +152,10 @@ constructor(
tileSpec.getLogTag(),
LogLevel.DEBUG,
{
- str1 = trigger.toLogString()
- str2 = tileState.toLogString()
- str3 = data.toString().take(DATA_MAX_LENGTH)
+ str1 = tileState.toLogString()
+ str2 = data.toString().take(DATA_MAX_LENGTH)
},
- { "tile state update: trigger=$str1, state=$str2, data=$str3" }
+ { "tile state update: state=$str1, data=$str2" }
)
}
@@ -154,14 +163,20 @@ constructor(
private fun TileSpec.getLogBuffer(): LogBuffer =
synchronized(logBufferCache) {
- logBufferCache.getOrPut(this) { defaultLogBufferProvider.get() }
+ logBufferCache.getOrPut(this) {
+ factory.create(
+ "QSTileLog_${this.getLogTag()}",
+ BUFFER_MAX_SIZE /* maxSize */,
+ false /* systrace */
+ )
+ }
}
- private fun StateUpdateTrigger.toLogString(): String =
+ private fun DataUpdateTrigger.toLogString(): String =
when (this) {
- is StateUpdateTrigger.ForceUpdate -> "force"
- is StateUpdateTrigger.InitialRequest -> "init"
- is StateUpdateTrigger.UserAction<*> -> action.toLogString()
+ is DataUpdateTrigger.ForceUpdate -> "force"
+ is DataUpdateTrigger.InitialRequest -> "init"
+ is DataUpdateTrigger.UserInput<*> -> input.action.toLogString()
}
private fun QSTileUserAction.toLogString(): String =
@@ -185,5 +200,6 @@ constructor(
private companion object {
const val TAG_FORMAT_PREFIX = "QSLog"
const val DATA_MAX_LENGTH = 50
+ const val BUFFER_MAX_SIZE = 25
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
index 2114751ef57b..14de5eb8be7f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -22,12 +22,12 @@ import com.android.internal.util.Preconditions
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
@@ -35,26 +35,31 @@ import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
-import com.android.systemui.util.kotlin.sample
+import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.throttle
+import com.android.systemui.util.time.SystemClock
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -66,6 +71,7 @@ import kotlinx.coroutines.flow.stateIn
*
* Inject [BaseQSTileViewModel.Factory] to create a new instance of this class.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
class BaseQSTileViewModel<DATA_TYPE>
@VisibleForTesting
constructor(
@@ -74,9 +80,11 @@ constructor(
private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
private val disabledByPolicyInteractor: DisabledByPolicyInteractor,
+ userRepository: UserRepository,
private val falsingManager: FalsingManager,
private val qsTileAnalytics: QSTileAnalytics,
private val qsTileLogger: QSTileLogger,
+ private val systemClock: SystemClock,
private val backgroundDispatcher: CoroutineDispatcher,
private val tileScope: CoroutineScope,
) : QSTileViewModel {
@@ -88,9 +96,11 @@ constructor(
@Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
@Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>,
disabledByPolicyInteractor: DisabledByPolicyInteractor,
+ userRepository: UserRepository,
falsingManager: FalsingManager,
qsTileAnalytics: QSTileAnalytics,
qsTileLogger: QSTileLogger,
+ systemClock: SystemClock,
@Background backgroundDispatcher: CoroutineDispatcher,
) : this(
config,
@@ -98,28 +108,30 @@ constructor(
tileDataInteractor,
mapper,
disabledByPolicyInteractor,
+ userRepository,
falsingManager,
qsTileAnalytics,
qsTileLogger,
+ systemClock,
backgroundDispatcher,
CoroutineScope(SupervisorJob())
)
+ private val userIds: MutableStateFlow<Int> =
+ MutableStateFlow(userRepository.getSelectedUserInfo().id)
private val userInputs: MutableSharedFlow<QSTileUserAction> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
- private val userIds: MutableSharedFlow<Int> =
- MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val forceUpdates: MutableSharedFlow<Unit> =
MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val spec
get() = config.tileSpec
- private lateinit var tileData: SharedFlow<DataWithTrigger<DATA_TYPE>>
+ private lateinit var tileData: SharedFlow<DATA_TYPE>
override lateinit var state: SharedFlow<QSTileState>
override val isAvailable: StateFlow<Boolean> =
- tileDataInteractor
- .availability()
+ userIds
+ .flatMapLatest { tileDataInteractor.availability(it) }
.flowOn(backgroundDispatcher)
.stateIn(
tileScope,
@@ -162,15 +174,9 @@ constructor(
tileData = createTileDataFlow()
state =
tileData
- // TODO(b/299908705): log data and corresponding tile state
- .map { dataWithTrigger ->
- mapper.map(config, dataWithTrigger.data).also { state ->
- qsTileLogger.logStateUpdate(
- spec,
- dataWithTrigger.trigger,
- state,
- dataWithTrigger.data
- )
+ .map { data ->
+ mapper.map(config, data).also { state ->
+ qsTileLogger.logStateUpdate(spec, state, data)
}
}
.flowOn(backgroundDispatcher)
@@ -188,88 +194,99 @@ constructor(
currentLifeState = lifecycle
}
- private fun createTileDataFlow(): SharedFlow<DataWithTrigger<DATA_TYPE>> =
+ private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
userIds
.flatMapLatest { userId ->
- merge(
- userInputFlow(userId),
- forceUpdates.map { StateUpdateTrigger.ForceUpdate },
- )
- .onStart { emit(StateUpdateTrigger.InitialRequest) }
- .map { trigger -> QSTileDataRequest(userId, trigger) }
- }
- .flatMapLatest { request ->
- // 1) get an updated data source
- // 2) process user input, possibly triggering new data to be emitted
- // This handles the case when the data isn't buffered in the interactor
- // TODO(b/299908705): Log events that trigger data flow to update
- val dataFlow = tileDataInteractor.tileData(request)
- if (request.trigger is StateUpdateTrigger.UserAction<*>) {
- userActionInteractor.handleInput(
- request.trigger.action,
- request.trigger.tileData as DATA_TYPE,
- )
- }
- dataFlow.map { DataWithTrigger(it, request.trigger) }
+ val updateTriggers =
+ merge(
+ userInputFlow(userId),
+ forceUpdates
+ .map { DataUpdateTrigger.ForceUpdate }
+ .onEach { qsTileLogger.logForceUpdate(spec) },
+ )
+ .onStart {
+ emit(DataUpdateTrigger.InitialRequest)
+ qsTileLogger.logInitialRequest(spec)
+ }
+ tileDataInteractor
+ .tileData(userId, updateTriggers)
+ .cancellable()
+ .flowOn(backgroundDispatcher)
}
- .flowOn(backgroundDispatcher)
.shareIn(
tileScope,
SharingStarted.WhileSubscribed(),
replay = 1, // we only care about the most recent value
)
- private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> {
- data class StateWithData<T>(val state: QSTileState, val data: T)
+ /**
+ * Creates a user input flow which:
+ * - filters false inputs with [falsingManager]
+ * - takes care of a tile being disable by policy using [disabledByPolicyInteractor]
+ * - notifies [userActionInteractor] about the action
+ * - logs it accordingly using [qsTileLogger] and [qsTileAnalytics]
+ *
+ * Subscribing to the result flow twice will result in doubling all actions, logs and analytics.
+ */
+ private fun userInputFlow(userId: Int): Flow<DataUpdateTrigger> {
+ return userInputs
+ .filterFalseActions()
+ .filterByPolicy(userId)
+ .throttle(CLICK_THROTTLE_DURATION, systemClock)
+ // Skip the input until there is some data
+ .mapNotNull { action ->
+ val state: QSTileState = state.replayCache.lastOrNull() ?: return@mapNotNull null
+ val data: DATA_TYPE = tileData.replayCache.lastOrNull() ?: return@mapNotNull null
+ qsTileLogger.logUserActionPipeline(spec, action, state, data)
+ qsTileAnalytics.trackUserAction(config, action)
- return when (config.policy) {
- is QSTilePolicy.NoRestrictions -> userInputs
- is QSTilePolicy.Restricted ->
- userInputs.filter { action ->
- val result =
- disabledByPolicyInteractor.isDisabled(
- userId,
- config.policy.userRestriction
- )
- !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
- if (isDisabled) {
- qsTileLogger.logUserActionRejectedByPolicy(action, spec)
- }
- }
- }
+ DataUpdateTrigger.UserInput(QSTileInput(userId, action, data))
}
- .filter { action ->
- val isFalseAction =
- when (action) {
- is QSTileUserAction.Click ->
- falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
- is QSTileUserAction.LongClick ->
- falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
+ .onEach { userActionInteractor.handleInput(it.input) }
+ .flowOn(backgroundDispatcher)
+ }
+
+ private fun Flow<QSTileUserAction>.filterByPolicy(userId: Int): Flow<QSTileUserAction> =
+ when (config.policy) {
+ is QSTilePolicy.NoRestrictions -> this
+ is QSTilePolicy.Restricted ->
+ filter { action ->
+ val result =
+ disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction)
+ !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
+ if (isDisabled) {
+ qsTileLogger.logUserActionRejectedByPolicy(action, spec)
+ }
}
- if (isFalseAction) {
- qsTileLogger.logUserActionRejectedByFalsing(action, spec)
}
- !isFalseAction
- }
- .throttle(500)
- // Skip the input until there is some data
- .sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) {
- input,
- stateWithData ->
- StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data).also {
- qsTileLogger.logUserActionPipeline(
- spec,
- it.action,
- stateWithData.state,
- stateWithData.data
- )
- qsTileAnalytics.trackUserAction(config, it.action)
+ }
+
+ private fun Flow<QSTileUserAction>.filterFalseActions(): Flow<QSTileUserAction> =
+ filter { action ->
+ val isFalseAction =
+ when (action) {
+ is QSTileUserAction.Click ->
+ falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)
+ is QSTileUserAction.LongClick ->
+ falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)
}
+ if (isFalseAction) {
+ qsTileLogger.logUserActionRejectedByFalsing(action, spec)
}
- }
+ !isFalseAction
+ }
- private data class DataWithTrigger<T>(val data: T, val trigger: StateUpdateTrigger)
+ private companion object {
+ const val CLICK_THROTTLE_DURATION = 200L
+ }
+ /**
+ * Factory interface for assisted inject. Dagger has bad time supporting generics in assisted
+ * injection factories now. That's why you need to create an interface implementing this one and
+ * annotate it with [dagger.assisted.AssistedFactory].
+ *
+ * ex: @AssistedFactory interface FooFactory : BaseQSTileViewModel.Factory<FooData>
+ */
interface Factory<T> {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 0ccde741e2cc..dc5ccccd6f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -59,7 +59,16 @@ data class QSTileState(
// This represents a tile that is currently in a disabled state but is still interactable. A
// disabled state indicates that the tile is not currently active (e.g. wifi disconnected or
// bluetooth disabled), but is still interactable by the user to modify this state.
- INACTIVE(Tile.STATE_INACTIVE),
+ INACTIVE(Tile.STATE_INACTIVE);
+
+ companion object {
+ fun valueOf(legacyState: Int): ActivationState =
+ when (legacyState) {
+ Tile.STATE_ACTIVE -> ACTIVE
+ Tile.STATE_INACTIVE -> INACTIVE
+ else -> UNAVAILABLE
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f6299e38ae18..33f55ab53233 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -22,6 +22,7 @@ import android.view.View
import androidx.annotation.GuardedBy
import com.android.internal.logging.InstanceId
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
@@ -31,9 +32,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.function.Supplier
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
@@ -44,6 +43,7 @@ import kotlinx.coroutines.launch
class QSTileViewModelAdapter
@AssistedInject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val qsHost: QSHost,
@Assisted private val qsTileViewModel: QSTileViewModel,
) : QSTile {
@@ -57,25 +57,28 @@ constructor(
private val listeningClients: MutableCollection<Any> = mutableSetOf()
// Cancels the jobs when the adapter is no longer alive
- private val adapterScope = CoroutineScope(SupervisorJob())
+ private var availabilityJob: Job? = null
// Cancels the jobs when clients stop listening
- private val listeningScope = CoroutineScope(SupervisorJob())
+ private var stateJob: Job? = null
init {
- adapterScope.launch {
- qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
- if (!isAvailable) {
- qsHost.removeTile(tileSpec)
- }
- // qsTileViewModel.isAvailable flow often starts with isAvailable == true. That's
- // why we only allow isAvailable == true once and throw an exception afterwards.
- if (index > 0 && isAvailable) {
- // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for additional
- // guidance on how to auto add your tile
- throw UnsupportedOperationException("Turning on tile is not supported now")
+ availabilityJob =
+ applicationScope.launch {
+ qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+ if (!isAvailable) {
+ qsHost.removeTile(tileSpec)
+ }
+ // qsTileViewModel.isAvailable flow often starts with isAvailable == true.
+ // That's
+ // why we only allow isAvailable == true once and throw an exception afterwards.
+ if (index > 0 && isAvailable) {
+ // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for
+ // additional
+ // guidance on how to auto add your tile
+ throw UnsupportedOperationException("Turning on tile is not supported now")
+ }
}
}
- }
// QSTileHost doesn't call this when userId is initialized
userSwitch(qsHost.userId)
@@ -140,25 +143,28 @@ constructor(
)
override fun getMetricsCategory(): Int = 0
+ override fun isTileReady(): Boolean = qsTileViewModel.currentState != null
+
override fun setListening(client: Any?, listening: Boolean) {
client ?: return
synchronized(listeningClients) {
if (listening) {
listeningClients.add(client)
if (listeningClients.size == 1) {
- qsTileViewModel.state
- .map { mapState(context, it, qsTileViewModel.config) }
- .onEach { legacyState ->
- synchronized(callbacks) {
- callbacks.forEach { it.onStateChanged(legacyState) }
+ stateJob =
+ qsTileViewModel.state
+ .map { mapState(context, it, qsTileViewModel.config) }
+ .onEach { legacyState ->
+ synchronized(callbacks) {
+ callbacks.forEach { it.onStateChanged(legacyState) }
+ }
}
- }
- .launchIn(listeningScope)
+ .launchIn(applicationScope)
}
} else {
listeningClients.remove(client)
if (listeningClients.isEmpty()) {
- listeningScope.coroutineContext.cancelChildren()
+ stateJob?.cancel()
}
}
}
@@ -172,8 +178,8 @@ constructor(
}
override fun destroy() {
- adapterScope.cancel()
- listeningScope.cancel()
+ stateJob?.cancel()
+ availabilityJob?.cancel()
qsTileViewModel.onLifecycle(QSTileLifecycle.DEAD)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index f0650d34fc9d..9ba02b1aa9a8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -17,6 +17,8 @@
package com.android.systemui.scene.shared.flag
import androidx.annotation.VisibleForTesting
+import com.android.systemui.FeatureFlags
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.compose.ComposeFacade
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
@@ -47,15 +49,15 @@ interface SceneContainerFlags {
class SceneContainerFlagsImpl
@AssistedInject
constructor(
- private val featureFlags: FeatureFlagsClassic,
+ private val featureFlagsClassic: FeatureFlagsClassic,
+ featureFlags: FeatureFlags,
@Assisted private val isComposeAvailable: Boolean,
) : SceneContainerFlags {
companion object {
@VisibleForTesting
- val flags: List<Flag<Boolean>> =
+ val classicFlagTokens: List<Flag<Boolean>> =
listOf(
- Flags.SCENE_CONTAINER,
Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA,
Flags.MIGRATE_LOCK_ICON,
Flags.MIGRATE_NSSL,
@@ -67,7 +69,13 @@ constructor(
/** The list of requirements, all must be met for the feature to be enabled. */
private val requirements =
- flags.map { FlagMustBeEnabled(it) } +
+ listOf(
+ AconfigFlagMustBeEnabled(
+ flagName = AConfigFlags.FLAG_SCENE_CONTAINER,
+ flagValue = featureFlags.sceneContainer(),
+ ),
+ ) +
+ classicFlagTokens.map { flagToken -> FlagMustBeEnabled(flagToken) } +
listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
override fun isEnabled(): Boolean {
@@ -115,14 +123,25 @@ constructor(
override fun isMet(): Boolean {
return when (flag) {
- is ResourceBooleanFlag -> featureFlags.isEnabled(flag)
- is ReleasedFlag -> featureFlags.isEnabled(flag)
- is UnreleasedFlag -> featureFlags.isEnabled(flag)
+ is ResourceBooleanFlag -> featureFlagsClassic.isEnabled(flag)
+ is ReleasedFlag -> featureFlagsClassic.isEnabled(flag)
+ is UnreleasedFlag -> featureFlagsClassic.isEnabled(flag)
else -> error("Unsupported flag type ${flag.javaClass}")
}
}
}
+ private inner class AconfigFlagMustBeEnabled(
+ flagName: String,
+ private val flagValue: Boolean,
+ ) : Requirement {
+ override val name: String = "Aconfig flag $flagName must be enabled"
+
+ override fun isMet(): Boolean {
+ return flagValue
+ }
+ }
+
@AssistedFactory
interface Factory {
fun create(isComposeAvailable: Boolean): SceneContainerFlagsImpl
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index f469c6b78e87..ea1205ae6079 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.CountDownTimer;
+import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
@@ -141,6 +142,13 @@ public class RecordingController
return UserHandle.of(UserHandle.myUserId());
}
+ /**
+ * MediaProjection host is SystemUI for the screen recorder, so return 'my process uid'
+ */
+ private int getHostUid() {
+ return Process.myUid();
+ }
+
/** Create a dialog to show screen recording options to the user.
* If screen capturing is currently not allowed it will return a dialog
* that warns users about it. */
@@ -155,12 +163,22 @@ public class RecordingController
}
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+ mUserContextProvider.getUserContext().getUserId(),
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? new ScreenRecordPermissionDialog(context, getHostUserHandle(), this,
- activityStarter, mUserContextProvider, onStartRecordingClicked)
- : new ScreenRecordDialog(context, this, mUserContextProvider,
+ ? new ScreenRecordPermissionDialog(
+ context,
+ getHostUserHandle(),
+ getHostUid(),
+ /* controller= */ this,
+ activityStarter,
+ mUserContextProvider,
+ onStartRecordingClicked)
+ : new ScreenRecordDialog(
+ context,
+ /* controller= */ this,
+ mUserContextProvider,
onStartRecordingClicked);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index ade93b1a913e..3b3aa5334979 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -46,6 +46,7 @@ import com.android.systemui.settings.UserContextProvider
class ScreenRecordPermissionDialog(
context: Context,
private val hostUserHandle: UserHandle,
+ private val hostUid: Int,
private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
@@ -88,6 +89,7 @@ class ScreenRecordPermissionDialog(
MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
hostUserHandle
)
+ intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid)
activityStarter.startActivity(intent, /* dismissShade= */ true)
}
dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index aa6bfc3473d2..10d5f597105a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -34,11 +34,11 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.settings.DisplayTracker
+import com.android.systemui.util.TraceUtils.Companion.launch
import javax.inject.Inject
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@SysUISingleton
@@ -63,7 +63,9 @@ constructor(
user: UserHandle,
overrideTransition: Boolean,
) {
- applicationScope.launch { launchIntent(intent, options, user, overrideTransition) }
+ applicationScope.launch("$TAG#launchIntentAsync") {
+ launchIntent(intent, options, user, overrideTransition)
+ }
}
suspend fun launchIntent(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index c34fd42e2154..f1c74c1bcff6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -20,10 +20,10 @@ import android.util.Log
import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.TraceUtils.Companion.launch
+import kotlinx.coroutines.CoroutineScope
import java.util.function.Consumer
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
/** Processes a screenshot request sent from [ScreenshotHelper]. */
interface ScreenshotRequestProcessor {
@@ -88,7 +88,7 @@ constructor(
* thread
*/
fun processAsync(screenshot: ScreenshotData, callback: Consumer<ScreenshotData>) {
- mainScope.launch {
+ mainScope.launch({ "$TAG#processAsync" }) {
val result = process(screenshot)
callback.accept(result)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 14e875d28f8f..d2e47946441b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -25,7 +25,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shade.ShadeExpansionStateManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.launch
+import com.android.systemui.util.TraceUtils.Companion.launch
import kotlinx.coroutines.withContext
/** Provides state from the main SystemUI process on behalf of the Screenshot process. */
@@ -47,7 +47,9 @@ constructor(
}
override fun dismissKeyguard(callback: IOnDoneCallback) {
- lifecycleScope.launch { executeAfterDismissing(callback) }
+ lifecycleScope.launch("IScreenshotProxy#dismissKeyguard") {
+ executeAfterDismissing(callback)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index cd0cab556c4d..1eae1918c4c7 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -20,7 +20,7 @@ import android.media.MediaPlayer
import android.util.Log
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.util.TraceUtils.Companion.tracedAsync
+import com.android.systemui.util.TraceUtils.Companion.async
import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@@ -48,7 +48,7 @@ constructor(
) : ScreenshotSoundController {
val player: Deferred<MediaPlayer?> =
- coroutineScope.tracedAsync("loadCameraSound", bgDispatcher) {
+ coroutineScope.async("loadCameraSound", bgDispatcher) {
try {
soundProvider.getScreenshotSound()
} catch (e: IllegalStateException) {
@@ -58,12 +58,10 @@ constructor(
}
override fun playCameraSound(): Deferred<Unit> {
- return coroutineScope.tracedAsync("playCameraSound", bgDispatcher) {
- player.await()?.start()
- }
+ return coroutineScope.async("playCameraSound", bgDispatcher) { player.await()?.start() }
}
override fun releaseScreenshotSound(): Deferred<Unit> {
- return coroutineScope.tracedAsync("releaseScreenshotSound", bgDispatcher) {
+ return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
try {
withTimeout(1.seconds) { player.await()?.release() }
} catch (e: TimeoutCancellationException) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 015828438375..5684605601c9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -13,14 +13,11 @@ import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.res.R
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback
+import com.android.systemui.util.TraceUtils.Companion.launch
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.first
/**
* Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the
@@ -40,12 +37,7 @@ constructor(
private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory,
) {
- private lateinit var displays: StateFlow<Set<Display>>
- private val displaysCollectionJob: Job =
- mainScope.launch {
- displays = displayRepository.displays.stateIn(this, SharingStarted.Eagerly, emptySet())
- }
-
+ private val displays = displayRepository.displays
private val screenshotControllers = mutableMapOf<Int, ScreenshotController>()
private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>()
@@ -63,6 +55,7 @@ constructor(
val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
displayIds.forEach { displayId: Int ->
+ Log.d(TAG, "Executing screenshot for display $displayId")
dispatchToController(
rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId),
onSaved =
@@ -126,12 +119,12 @@ constructor(
callback.reportError()
}
- private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
+ private suspend fun getDisplaysToScreenshot(requestType: Int): List<Int> {
return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
// If this is a provided image, let's show the UI on the default display only.
listOf(Display.DEFAULT_DISPLAY)
} else {
- displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+ displays.first().filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
}
}
@@ -163,7 +156,6 @@ constructor(
screenshotController.onDestroy()
}
screenshotControllers.clear()
- displaysCollectionJob.cancel()
}
private fun getScreenshotController(id: Int): ScreenshotController {
@@ -184,7 +176,7 @@ constructor(
onSaved: Consumer<Uri>,
requestCallback: RequestCallback
) {
- mainScope.launch {
+ mainScope.launch("TakeScreenshotService#executeScreenshotsAsync") {
executeScreenshots(screenshotRequest, { uri -> onSaved.accept(uri) }, requestCallback)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
index cf1fbe3685e3..d6f1ed9c3334 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt
@@ -68,6 +68,10 @@ interface UserTracker : UserContentResolverProvider, UserContextProvider {
*/
@WeaklyReferencedCallback
interface Callback {
+ /**
+ * Notifies that the current user will be changed.
+ */
+ fun onBeforeUserSwitching(newUser: Int) {}
/**
* Same as {@link onUserChanging(Int, Context, Runnable)} but the callback will be
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 99127ea928bf..9f416bbf2c59 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -227,6 +227,13 @@ open class UserTrackerImpl internal constructor(
protected open fun handleBeforeUserSwitching(newUserId: Int) {
Assert.isNotMainThread()
setUserIdInternal(newUserId)
+
+ val list = synchronized(callbacks) {
+ callbacks.toList()
+ }
+ list.forEach {
+ it.callback.get()?.onBeforeUserSwitching(newUserId)
+ }
}
@WorkerThread
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 1ecb127f0c92..ead10d6da860 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -16,6 +16,7 @@
package com.android.systemui.settings.brightness;
+import static android.content.Intent.EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
@@ -83,7 +84,7 @@ public class BrightnessDialog extends Activity {
private void setWindowAttributes() {
final Window window = getWindow();
- window.setGravity(Gravity.TOP | Gravity.LEFT);
+ window.setGravity(Gravity.TOP | Gravity.START);
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
window.requestFeature(Window.FEATURE_NO_TITLE);
@@ -130,13 +131,14 @@ public class BrightnessDialog extends Activity {
Configuration configuration = getResources().getConfiguration();
int orientation = configuration.orientation;
+ int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2
- - lp.leftMargin * 2;
+ boolean shouldBeFullWidth = getIntent()
+ .getBooleanExtra(EXTRA_BRIGHTNESS_DIALOG_IS_FULL_WIDTH, false);
+ lp.width = (shouldBeFullWidth ? screenWidth : screenWidth / 2) - horizontalMargin * 2;
} else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- lp.width = getWindowManager().getDefaultDisplay().getWidth()
- - lp.leftMargin * 2;
+ lp.width = screenWidth - horizontalMargin * 2;
}
frame.setLayoutParams(lp);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 09514404d4db..a2627eddf05d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,10 +42,13 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.data.repository.CommunalRepository;
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel;
+import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
@@ -62,7 +65,7 @@ import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -103,8 +106,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
private final PulsingGestureListener mPulsingGestureListener;
private final LockscreenHostedDreamGestureListener mLockscreenHostedDreamGestureListener;
private final NotificationInsetsController mNotificationInsetsController;
+ private final CommunalViewModel mCommunalViewModel;
+ private final CommunalRepository mCommunalRepository;
private final boolean mIsTrackpadCommonEnabled;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlagsClassic;
private final SysUIKeyEventHandler mSysUIKeyEventHandler;
private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@@ -175,8 +180,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
KeyguardTransitionInteractor keyguardTransitionInteractor,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
- NotificationExpansionRepository notificationExpansionRepository,
- FeatureFlags featureFlags,
+ CommunalViewModel communalViewModel,
+ CommunalRepository communalRepository,
+ NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
+ FeatureFlagsClassic featureFlagsClassic,
SystemClock clock,
BouncerMessageInteractor bouncerMessageInteractor,
BouncerLogger bouncerLogger,
@@ -206,8 +213,10 @@ public class NotificationShadeWindowViewController implements Dumpable {
mPulsingGestureListener = pulsingGestureListener;
mLockscreenHostedDreamGestureListener = lockscreenHostedDreamGestureListener;
mNotificationInsetsController = notificationInsetsController;
- mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON);
- mFeatureFlags = featureFlags;
+ mCommunalViewModel = communalViewModel;
+ mCommunalRepository = communalRepository;
+ mIsTrackpadCommonEnabled = featureFlagsClassic.isEnabled(TRACKPAD_GESTURE_COMMON);
+ mFeatureFlagsClassic = featureFlagsClassic;
mSysUIKeyEventHandler = sysUIKeyEventHandler;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -223,18 +232,18 @@ public class NotificationShadeWindowViewController implements Dumpable {
messageAreaControllerFactory,
bouncerMessageInteractor,
bouncerLogger,
- featureFlags,
+ featureFlagsClassic,
selectedUserInteractor);
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
mLockscreenToDreamingTransition);
collectFlow(
mView,
- notificationExpansionRepository.isExpandAnimationRunning(),
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning(),
this::setExpandAnimationRunning);
mClock = clock;
- if (featureFlags.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
+ if (featureFlagsClassic.isEnabled(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION)) {
unfoldTransitionProgressProvider.ifPresent(
progressProvider -> progressProvider.addCallback(
mDisableSubpixelTextTransitionListener));
@@ -263,7 +272,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
- if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
+ if (mFeatureFlagsClassic.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) {
mDreamingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mLockscreenHostedDreamGestureListener);
}
@@ -435,7 +444,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
}
boolean bouncerShowing;
- if (mFeatureFlags.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.ALTERNATE_BOUNCER_VIEW)) {
bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing()
|| mAlternateBouncerInteractor.isVisibleState();
} else {
@@ -447,7 +456,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (mDragDownHelper.isDragDownEnabled()) {
// This handles drag down over lockscreen
boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
if (result) {
mLastInterceptWasDragDownHelper = true;
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -479,7 +488,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (!mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
}
cancellation.recycle();
@@ -494,7 +503,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (mStatusBarKeyguardViewManager.onTouch(ev)) {
return true;
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
// we still want to finish our drag down gesture when locking the screen
handled |= mDragDownHelper.onTouchEvent(ev) || handled;
@@ -559,8 +568,28 @@ public class NotificationShadeWindowViewController implements Dumpable {
mDepthController.onPanelExpansionChanged(currentState);
}
+ /**
+ * Sets up the communal hub UI if the {@link com.android.systemui.Flags#FLAG_COMMUNAL_HUB} flag
+ * is enabled.
+ *
+ * The layout lives in {@link R.id.communal_ui_container}.
+ */
+ public void setupCommunalHubLayout() {
+ if (!mCommunalRepository.isCommunalEnabled()
+ || !ComposeFacade.INSTANCE.isComposeAvailable()) {
+ return;
+ }
+
+ // Replace the placeholder view with the communal UI.
+ View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+ int index = mView.indexOfChild(communalPlaceholder);
+ mView.removeView(communalPlaceholder);
+ mView.addView(ComposeFacade.INSTANCE.createCommunalContainer(mView.getContext(),
+ mCommunalViewModel), index);
+ }
+
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ if (mFeatureFlagsClassic.isEnabled(Flags.MIGRATE_NSSL)) {
// Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
// to also ask NotificationPanelViewController directly, in order to process swipe up
// events originating from notifications
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index a8a20cc8559b..2f684762a13a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -35,7 +35,6 @@ import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
import com.android.systemui.statusbar.policy.data.repository.DeviceProvisioningRepository
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
-import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -140,13 +139,6 @@ constructor(
/** Whether either the shade or QS is fully expanded. */
val isAnyFullyExpanded: Flow<Boolean> = anyExpansion.map { it >= 1f }.distinctUntilChanged()
- /** Whether either the shade or QS is expanding from a fully collapsed state. */
- val isAnyExpanding: Flow<Boolean> =
- anyExpansion
- .pairwise(1f)
- .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f }
- .distinctUntilChanged()
-
/**
* Whether either the shade or QS is partially or fully expanded, i.e. not fully collapsed. At
* this time, this is not simply a matter of checking if either value in shadeExpansion and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java b/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java
deleted file mode 100644
index f1eb9febfb33..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BackDropView.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.FrameLayout;
-
-/**
- * A view who contains media artwork.
- */
-public class BackDropView extends FrameLayout
-{
- private Runnable mOnVisibilityChangedRunnable;
-
- public BackDropView(Context context) {
- super(context);
- }
-
- public BackDropView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public BackDropView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- public BackDropView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (changedView == this && mOnVisibilityChangedRunnable != null) {
- mOnVisibilityChangedRunnable.run();
- }
- }
-
- public void setOnVisibilityChangedRunnable(Runnable runnable) {
- mOnVisibilityChangedRunnable = runnable;
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 77b095802b00..7d81e55d336a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar;
+import android.animation.Animator;
import android.view.View;
+import android.view.ViewPropertyAnimator;
import androidx.annotation.Nullable;
@@ -31,32 +33,58 @@ public class CrossFadeHelper {
public static final long ANIMATION_DURATION_LENGTH = 210;
public static void fadeOut(final View view) {
- fadeOut(view, null);
+ fadeOut(view, (Runnable) null);
}
public static void fadeOut(final View view, final Runnable endRunnable) {
fadeOut(view, ANIMATION_DURATION_LENGTH, 0, endRunnable);
}
+ public static void fadeOut(final View view, final Animator.AnimatorListener listener) {
+ fadeOut(view, ANIMATION_DURATION_LENGTH, 0, listener);
+ }
+
+ public static void fadeOut(final View view, long duration, int delay) {
+ fadeOut(view, duration, delay, (Runnable) null);
+ }
+
public static void fadeOut(final View view, long duration, int delay,
- final Runnable endRunnable) {
+ @Nullable final Runnable endRunnable) {
view.animate().cancel();
view.animate()
.alpha(0f)
.setDuration(duration)
.setInterpolator(Interpolators.ALPHA_OUT)
.setStartDelay(delay)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- if (view.getVisibility() != View.GONE) {
- view.setVisibility(View.INVISIBLE);
- }
+ .withEndAction(() -> {
+ if (endRunnable != null) {
+ endRunnable.run();
+ }
+ if (view.getVisibility() != View.GONE) {
+ view.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (view.hasOverlappingRendering()) {
+ view.animate().withLayer();
+ }
+ }
+
+ public static void fadeOut(final View view, long duration, int delay,
+ @Nullable final Animator.AnimatorListener listener) {
+ view.animate().cancel();
+ ViewPropertyAnimator animator = view.animate()
+ .alpha(0f)
+ .setDuration(duration)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .setStartDelay(delay)
+ .withEndAction(() -> {
+ if (view.getVisibility() != View.GONE) {
+ view.setVisibility(View.INVISIBLE);
}
});
+ if (listener != null) {
+ animator.setListener(listener);
+ }
if (view.hasOverlappingRendering()) {
view.animate().withLayer();
}
@@ -119,8 +147,12 @@ public class CrossFadeHelper {
fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, endRunnable);
}
+ public static void fadeIn(final View view, Animator.AnimatorListener listener) {
+ fadeIn(view, ANIMATION_DURATION_LENGTH, /* delay= */ 0, listener);
+ }
+
public static void fadeIn(final View view, long duration, int delay) {
- fadeIn(view, duration, delay, /* endRunnable= */ null);
+ fadeIn(view, duration, delay, /* endRunnable= */ (Runnable) null);
}
public static void fadeIn(final View view, long duration, int delay,
@@ -141,6 +173,26 @@ public class CrossFadeHelper {
}
}
+ public static void fadeIn(final View view, long duration, int delay,
+ @Nullable Animator.AnimatorListener listener) {
+ view.animate().cancel();
+ if (view.getVisibility() == View.INVISIBLE) {
+ view.setAlpha(0.0f);
+ view.setVisibility(View.VISIBLE);
+ }
+ ViewPropertyAnimator animator = view.animate()
+ .alpha(1f)
+ .setDuration(duration)
+ .setStartDelay(delay)
+ .setInterpolator(Interpolators.ALPHA_IN);
+ if (listener != null) {
+ animator.setListener(listener);
+ }
+ if (view.hasOverlappingRendering() && view.getLayerType() != View.LAYER_TYPE_HARDWARE) {
+ view.animate().withLayer();
+ }
+ }
+
public static void fadeIn(View view, float fadeInAmount) {
fadeIn(view, fadeInAmount, false /* remap */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index ff4570ea0fc5..2e1e395518a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -29,8 +29,11 @@ import android.util.Log;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
@@ -59,7 +62,9 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
private static final long MAX_RANKING_DELAY_MILLIS = 500L;
private final Context mContext;
+ private final FeatureFlagsClassic mFeatureFlags;
private final NotificationManager mNotificationManager;
+ private final SilentNotificationStatusIconsVisibilityInteractor mStatusIconInteractor;
private final SystemClock mSystemClock;
private final Executor mMainExecutor;
private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
@@ -75,13 +80,17 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
@Inject
public NotificationListener(
Context context,
+ FeatureFlagsClassic featureFlags,
NotificationManager notificationManager,
+ SilentNotificationStatusIconsVisibilityInteractor statusIconInteractor,
SystemClock systemClock,
@Main Executor mainExecutor,
PluginManager pluginManager) {
super(pluginManager);
mContext = context;
+ mFeatureFlags = featureFlags;
mNotificationManager = notificationManager;
+ mStatusIconInteractor = statusIconInteractor;
mSystemClock = systemClock;
mMainExecutor = mainExecutor;
}
@@ -95,6 +104,7 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
}
/** Registers a listener that's notified when any notification-related settings change. */
+ @Deprecated
public void addNotificationSettingsListener(NotificationSettingsListener listener) {
mSettingsListeners.add(listener);
}
@@ -230,8 +240,12 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
@Override
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
- for (NotificationSettingsListener listener : mSettingsListeners) {
- listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mStatusIconInteractor.setHideSilentStatusIcons(hideSilentStatusIcons);
+ } else {
+ for (NotificationSettingsListener listener : mSettingsListeners) {
+ listener.onStatusBarIconsBehaviorChanged(hideSilentStatusIcons);
+ }
}
}
@@ -294,6 +308,7 @@ public class NotificationListener extends NotificationListenerWithPlugins implem
return ranking;
}
+ @Deprecated
public interface NotificationSettingsListener {
default void onStatusBarIconsBehaviorChanged(boolean hideSilentStatusIcons) { }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index f32f1c2dcd25..710e59d91c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_
import static android.os.UserHandle.USER_NULL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static android.os.Flags.allowPrivateProfile;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -79,6 +80,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.Objects;
import javax.inject.Inject;
@@ -177,57 +179,50 @@ public class NotificationLockscreenUserManagerImpl implements
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- switch (action) {
- case Intent.ACTION_USER_REMOVED:
- int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (removedUserId != -1) {
- for (UserChangedListener listener : mListeners) {
- listener.onUserRemoved(removedUserId);
- }
- }
- updateCurrentProfilesCache();
- break;
- case Intent.ACTION_USER_ADDED:
- updateCurrentProfilesCache();
- if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
- mBackgroundHandler.post(() -> {
- initValuesForUser(userId);
- });
- }
- break;
- case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
- case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
- updateCurrentProfilesCache();
- break;
- case Intent.ACTION_USER_UNLOCKED:
- // Start the overview connection to the launcher service
- // Connect if user hasn't connected yet
- if (mOverviewProxyServiceLazy.get().getProxy() == null) {
- mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
- }
- break;
- case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
- final IntentSender intentSender = intent.getParcelableExtra(
- Intent.EXTRA_INTENT);
- final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
- if (intentSender != null) {
- try {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
- mContext.startIntentSender(intentSender, null, 0, 0, 0,
- options.toBundle());
- } catch (IntentSender.SendIntentException e) {
- /* ignore */
- }
+ if (Objects.equals(action, Intent.ACTION_USER_REMOVED)) {
+ int removedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (removedUserId != -1) {
+ for (UserChangedListener listener : mListeners) {
+ listener.onUserRemoved(removedUserId);
}
- if (notificationKey != null) {
- final NotificationVisibility nv = mVisibilityProviderLazy.get()
- .obtain(notificationKey, true);
- mClickNotifier.onNotificationClick(notificationKey, nv);
+ }
+ updateCurrentProfilesCache();
+ } else if (Objects.equals(action, Intent.ACTION_USER_ADDED)){
+ updateCurrentProfilesCache();
+ if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ mBackgroundHandler.post(() -> {
+ initValuesForUser(userId);
+ });
+ }
+ } else if (profileAvailabilityActions(action)) {
+ updateCurrentProfilesCache();
+ } else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) {
+ // Start the overview connection to the launcher service
+ // Connect if user hasn't connected yet
+ if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+ mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ }
+ } else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) {
+ final IntentSender intentSender = intent.getParcelableExtra(
+ Intent.EXTRA_INTENT);
+ final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+ if (intentSender != null) {
+ try {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(intentSender, null, 0, 0, 0,
+ options.toBundle());
+ } catch (IntentSender.SendIntentException e) {
+ /* ignore */
}
- break;
+ }
+ if (notificationKey != null) {
+ final NotificationVisibility nv = mVisibilityProviderLazy.get()
+ .obtain(notificationKey, true);
+ mClickNotifier.onNotificationClick(notificationKey, nv);
+ }
}
}
};
@@ -403,6 +398,10 @@ public class NotificationLockscreenUserManagerImpl implements
filter.addAction(Intent.ACTION_USER_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ if (allowPrivateProfile()){
+ filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
+ filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
+ }
mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter,
null /* executor */, UserHandle.ALL);
@@ -814,6 +813,14 @@ public class NotificationLockscreenUserManagerImpl implements
}
}
+ private boolean profileAvailabilityActions(String action){
+ return allowPrivateProfile()?
+ Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)||
+ Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE):
+ Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)||
+ Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 389486f0ada3..9c4625e91110 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -18,31 +18,21 @@ package com.android.systemui.statusbar;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
-import android.app.WallpaperManager;
import android.content.Context;
-import android.graphics.Point;
import android.graphics.drawable.Icon;
-import android.hardware.display.DisplayManager;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
-import android.os.Trace;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.util.Log;
-import android.view.Display;
-import android.view.View;
-import android.widget.ImageView;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.controls.models.player.MediaData;
import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -50,21 +40,13 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.LockscreenWallpaper;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Comparator;
import java.util.HashSet;
-import java.util.List;
import java.util.Objects;
import java.util.Optional;
-import java.util.stream.Collectors;
/**
* Handles tasks and state related to media notifications. For example, there is a 'current' media
@@ -74,9 +56,6 @@ public class NotificationMediaManager implements Dumpable {
private static final String TAG = "NotificationMediaManager";
public static final boolean DEBUG_MEDIA = false;
- private final StatusBarStateController mStatusBarStateController;
- private final SysuiColorExtractor mColorExtractor;
- private final KeyguardStateController mKeyguardStateController;
private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
static {
@@ -93,15 +72,6 @@ public class NotificationMediaManager implements Dumpable {
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
- @Nullable
- private BiometricUnlockController mBiometricUnlockController;
- @Nullable
- private ScrimController mScrimController;
- @Nullable
- private LockscreenWallpaper mLockscreenWallpaper;
- @VisibleForTesting
- boolean mIsLockscreenLiveWallpaperEnabled;
-
private final Context mContext;
private final ArrayList<MediaListener> mMediaListeners;
@@ -110,16 +80,6 @@ public class NotificationMediaManager implements Dumpable {
private String mMediaNotificationKey;
private MediaMetadata mMediaMetadata;
- private BackDropView mBackdrop;
- private ImageView mBackdropFront;
- private ImageView mBackdropBack;
- private final Point mTmpDisplaySize = new Point();
-
- private final DisplayManager mDisplayManager;
- @Nullable
- private List<String> mSmallerInternalDisplayUids;
- private Display mCurrentDisplay;
-
private final MediaController.Callback mMediaListener = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
@@ -142,7 +102,7 @@ public class NotificationMediaManager implements Dumpable {
Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
}
mMediaMetadata = metadata;
- dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
+ dispatchUpdateMediaMetaData();
}
};
@@ -155,23 +115,13 @@ public class NotificationMediaManager implements Dumpable {
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- StatusBarStateController statusBarStateController,
- SysuiColorExtractor colorExtractor,
- KeyguardStateController keyguardStateController,
- DumpManager dumpManager,
- WallpaperManager wallpaperManager,
- DisplayManager displayManager) {
+ DumpManager dumpManager) {
mContext = context;
mMediaListeners = new ArrayList<>();
mVisibilityProvider = visibilityProvider;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
- mStatusBarStateController = statusBarStateController;
- mColorExtractor = colorExtractor;
- mKeyguardStateController = keyguardStateController;
- mDisplayManager = displayManager;
- mIsLockscreenLiveWallpaperEnabled = wallpaperManager.isLockscreenLiveWallpaperEnabled();
setupNotifPipeline();
@@ -275,7 +225,7 @@ public class NotificationMediaManager implements Dumpable {
public void onNotificationRemoved(String key) {
if (key.equals(mMediaNotificationKey)) {
clearCurrentMediaNotification();
- dispatchUpdateMediaMetaData(true /* changed */, true /* allowEnterAnimation */);
+ dispatchUpdateMediaMetaData();
}
}
@@ -309,21 +259,18 @@ public class NotificationMediaManager implements Dumpable {
}
public void findAndUpdateMediaNotifications() {
- boolean metaDataChanged;
// TODO(b/169655907): get the semi-filtered notifications for current user
Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
- metaDataChanged = findPlayingMediaNotification(allNotifications);
- dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
+ findPlayingMediaNotification(allNotifications);
+ dispatchUpdateMediaMetaData();
}
/**
* Find a notification and media controller associated with the playing media session, and
* update this manager's internal state.
- * @return whether the current MediaMetadata changed (and needs to be announced to listeners).
+ * TODO(b/273443374) check this method
*/
- boolean findPlayingMediaNotification(
- @NonNull Collection<NotificationEntry> allNotifications) {
- boolean metaDataChanged = false;
+ void findPlayingMediaNotification(@NonNull Collection<NotificationEntry> allNotifications) {
// Promote the media notification with a controller in 'playing' state, if any.
NotificationEntry mediaNotification = null;
MediaController controller = null;
@@ -359,8 +306,6 @@ public class NotificationMediaManager implements Dumpable {
Log.v(TAG, "DEBUG_MEDIA: insert listener, found new controller: "
+ mMediaController + ", receive metadata: " + mMediaMetadata);
}
-
- metaDataChanged = true;
}
if (mediaNotification != null
@@ -371,8 +316,6 @@ public class NotificationMediaManager implements Dumpable {
+ mMediaNotificationKey);
}
}
-
- return metaDataChanged;
}
public void clearCurrentMediaNotification() {
@@ -380,10 +323,7 @@ public class NotificationMediaManager implements Dumpable {
clearCurrentMediaNotificationSession();
}
- private void dispatchUpdateMediaMetaData(boolean changed, boolean allowEnterAnimation) {
- if (mPresenter != null) {
- mPresenter.updateMediaMetaData(changed, allowEnterAnimation);
- }
+ private void dispatchUpdateMediaMetaData() {
@PlaybackState.State int state = getMediaControllerPlaybackState(mMediaController);
ArrayList<MediaListener> callbacks = new ArrayList<>(mMediaListeners);
for (int i = 0; i < callbacks.size(); i++) {
@@ -446,125 +386,6 @@ public class NotificationMediaManager implements Dumpable {
mMediaController = null;
}
- /**
- * Notify lockscreen wallpaper drawable the current internal display.
- */
- public void onDisplayUpdated(Display display) {
- Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
- mCurrentDisplay = display;
- Trace.endSection();
- }
-
- private boolean isOnSmallerInternalDisplays() {
- if (mSmallerInternalDisplayUids == null) {
- mSmallerInternalDisplayUids = findSmallerInternalDisplayUids();
- }
- return mSmallerInternalDisplayUids.contains(mCurrentDisplay.getUniqueId());
- }
-
- private List<String> findSmallerInternalDisplayUids() {
- if (mSmallerInternalDisplayUids != null) {
- return mSmallerInternalDisplayUids;
- }
- List<Display> internalDisplays = Arrays.stream(mDisplayManager.getDisplays(
- DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED))
- .filter(display -> display.getType() == Display.TYPE_INTERNAL)
- .collect(Collectors.toList());
- if (internalDisplays.isEmpty()) {
- return List.of();
- }
- Display largestDisplay = internalDisplays.stream()
- .max(Comparator.comparingInt(this::getRealDisplayArea))
- .orElse(internalDisplays.get(0));
- internalDisplays.remove(largestDisplay);
- return internalDisplays.stream().map(Display::getUniqueId).collect(Collectors.toList());
- }
-
- private int getRealDisplayArea(Display display) {
- display.getRealSize(mTmpDisplaySize);
- return mTmpDisplaySize.x * mTmpDisplaySize.y;
- }
-
- /**
- * Update media state of lockscreen media views and controllers .
- */
- public void updateMediaMetaData(boolean metaDataChanged) {
-
- if (mIsLockscreenLiveWallpaperEnabled) return;
-
- Trace.beginSection("CentralSurfaces#updateMediaMetaData");
- if (getBackDropView() == null) {
- Trace.endSection();
- return; // called too early
- }
-
- boolean wakeAndUnlock = mBiometricUnlockController != null
- && mBiometricUnlockController.isWakeAndUnlock();
- if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) {
- mBackdrop.setVisibility(View.INVISIBLE);
- Trace.endSection();
- return;
- }
-
- MediaMetadata mediaMetadata = getMediaMetadata();
-
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
- + getMediaNotificationKey()
- + " metadata=" + mediaMetadata
- + " metaDataChanged=" + metaDataChanged
- + " state=" + mStatusBarStateController.getState());
- }
-
- mColorExtractor.setHasMediaArtwork(false);
- if (mScrimController != null) {
- mScrimController.setHasBackdrop(false);
- }
-
- Trace.endSection();
- }
-
- public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
- ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) {
- mBackdrop = backdrop;
- mBackdropFront = backdropFront;
- mBackdropBack = backdropBack;
- mScrimController = scrimController;
- mLockscreenWallpaper = lockscreenWallpaper;
- }
-
- public void setBiometricUnlockController(BiometricUnlockController biometricUnlockController) {
- mBiometricUnlockController = biometricUnlockController;
- }
-
- /**
- * Hide the album artwork that is fading out and release its bitmap.
- */
- protected final Runnable mHideBackdropFront = new Runnable() {
- @Override
- public void run() {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: removing fade layer");
- }
- mBackdropFront.setVisibility(View.INVISIBLE);
- mBackdropFront.animate().cancel();
- mBackdropFront.setImageDrawable(null);
- }
- };
-
- // TODO(b/273443374): remove
- public boolean isLockscreenWallpaperOnNotificationShade() {
- return mBackdrop != null && mLockscreenWallpaper != null
- && !mLockscreenWallpaper.isLockscreenLiveWallpaperEnabled()
- && (mBackdropFront.isVisibleToUser() || mBackdropBack.isVisibleToUser());
- }
-
- // TODO(b/273443374) temporary test helper; remove
- @VisibleForTesting
- BackDropView getBackDropView() {
- return mBackdrop;
- }
-
public interface MediaListener {
/**
* Called whenever there's new metadata or playback state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 5dcf6d1d8f28..f3b5ab6968a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -32,11 +32,6 @@ public interface NotificationPresenter extends ExpandableNotificationRow.OnExpan
boolean isPresenterFullyCollapsed();
/**
- * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
- */
- void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
-
- /**
* Called when the current user changes.
* @param newUserId new user id
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 17da015789ea..ffde8c03682f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -118,16 +118,27 @@ public class RemoteInputController {
// If the view is being removed, this may be called even though we're not active
boolean remoteInputActiveForEntry = isRemoteInputActive(entry);
+ boolean remoteInputActive = isRemoteInputActive();
mLogger.logRemoveRemoteInput(
entry.getKey() /* entryKey */,
entry.mRemoteEditImeVisible /* remoteEditImeVisible */,
entry.mRemoteEditImeAnimatingAway /* remoteEditImeAnimatingAway */,
remoteInputActiveForEntry /* isRemoteInputActiveForEntry */,
- isRemoteInputActive()/* isRemoteInputActive */,
+ remoteInputActive/* isRemoteInputActive */,
reason/* reason */,
entry.getNotificationStyle()/* notificationStyle */);
- if (!remoteInputActiveForEntry) return;
+ if (!remoteInputActiveForEntry) {
+ if (mLastAppliedRemoteInputActive != null
+ && mLastAppliedRemoteInputActive
+ && !remoteInputActive) {
+ mLogger.logRemoteInputApplySkipped(
+ entry.getKey() /* entryKey */,
+ reason/* reason */,
+ entry.getNotificationStyle()/* notificationStyle */);
+ }
+ return;
+ }
pruneWeakThenRemoveAndContains(null /* contains */, entry /* remove */, token);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 125c8efe1884..1fe6b83b47b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -16,9 +16,7 @@
package com.android.systemui.statusbar.dagger;
-import android.app.WallpaperManager;
import android.content.Context;
-import android.hardware.display.DisplayManager;
import android.os.RemoteException;
import android.service.dreams.IDreamManager;
import android.util.Log;
@@ -29,7 +27,6 @@ import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.AnimationFeatureFlags;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.DumpManager;
@@ -121,24 +118,14 @@ public interface CentralSurfacesDependenciesModule {
NotifPipeline notifPipeline,
NotifCollection notifCollection,
MediaDataManager mediaDataManager,
- StatusBarStateController statusBarStateController,
- SysuiColorExtractor colorExtractor,
- KeyguardStateController keyguardStateController,
- DumpManager dumpManager,
- WallpaperManager wallpaperManager,
- DisplayManager displayManager) {
+ DumpManager dumpManager) {
return new NotificationMediaManager(
context,
visibilityProvider,
notifPipeline,
notifCollection,
mediaDataManager,
- statusBarStateController,
- colorExtractor,
- keyguardStateController,
- dumpManager,
- wallpaperManager,
- displayManager);
+ dumpManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index e2de37fcbcbe..22912df71334 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -17,18 +17,13 @@
package com.android.systemui.statusbar.dagger
import com.android.systemui.CoreStartable
-import com.android.systemui.statusbar.core.StatusBarInitializer
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepository
-import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryImpl
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepository
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryImpl
+import com.android.systemui.statusbar.data.StatusBarDataLayerModule
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import dagger.multibindings.IntoSet
/**
* A module for **only** classes related to the status bar **UI element**. This module specifically
@@ -38,24 +33,9 @@ import dagger.multibindings.IntoSet
* ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule],
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
-@Module
+@Module(includes = [StatusBarDataLayerModule::class])
abstract class StatusBarModule {
@Binds
- abstract fun bindStatusBarModeRepository(
- impl: StatusBarModeRepositoryImpl
- ): StatusBarModeRepository
-
- @Binds
- @IntoMap
- @ClassKey(StatusBarModeRepositoryImpl::class)
- abstract fun bindStatusBarModeRepositoryStart(impl: StatusBarModeRepositoryImpl): CoreStartable
-
- @Binds
- abstract fun bindKeyguardStatusBarRepository(
- impl: KeyguardStatusBarRepositoryImpl
- ): KeyguardStatusBarRepository
-
- @Binds
@IntoMap
@ClassKey(OngoingCallController::class)
abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
@@ -64,10 +44,4 @@ abstract class StatusBarModule {
@IntoMap
@ClassKey(LightBarController::class)
abstract fun bindLightBarController(impl: LightBarController): CoreStartable
-
- @Binds
- @IntoSet
- abstract fun statusBarInitializedListener(
- statusBarModeRepository: StatusBarModeRepository,
- ): StatusBarInitializer.OnStatusBarViewInitializedListener
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
new file mode 100644
index 000000000000..29d53fc15e8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.data
+
+import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
+import dagger.Module
+
+@Module(
+ includes =
+ [
+ KeyguardStatusBarRepositoryModule::class,
+ StatusBarModeRepositoryModule::class,
+ StatusBarPhoneDataLayerModule::class
+ ]
+)
+object StatusBarDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
index 8136de9b7ac4..d1594ef2e404 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/KeyguardStatusBarRepository.kt
@@ -22,6 +22,8 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.user.data.repository.UserSwitcherRepository
+import dagger.Binds
+import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -78,3 +80,8 @@ constructor(
isEnabled && isKeyguardEnabled
}
}
+
+@Module
+interface KeyguardStatusBarRepositoryModule {
+ @Binds fun bindImpl(impl: KeyguardStatusBarRepositoryImpl): KeyguardStatusBarRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
new file mode 100644
index 000000000000..2c706a5327f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/NotificationListenerSettingsRepository.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.NotificationListener
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Exposes state pertaining to settings tracked over the [NotificationListener] boundary. */
+@SysUISingleton
+class NotificationListenerSettingsRepository @Inject constructor() {
+ /** Should icons for silent notifications be shown in the status bar? */
+ val showSilentStatusIcons = MutableStateFlow(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
index 2b059944798f..47994d92d22b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepository.kt
@@ -30,7 +30,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.core.StatusBarInitializer
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
import com.android.systemui.statusbar.phone.BoundsPair
@@ -38,6 +38,11 @@ import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import dagger.multibindings.IntoSet
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -56,7 +61,7 @@ import kotlinx.coroutines.flow.stateIn
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModeRepository : StatusBarInitializer.OnStatusBarViewInitializedListener {
+interface StatusBarModeRepository {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -112,7 +117,7 @@ constructor(
private val commandQueue: CommandQueue,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModeRepository, CoreStartable {
+) : StatusBarModeRepository, CoreStartable, OnStatusBarViewInitializedListener {
private val commandQueueCallback =
object : CommandQueue.Callbacks {
@@ -334,3 +339,17 @@ constructor(
val statusBarBounds: BoundsPair,
)
}
+
+@Module
+interface StatusBarModeRepositoryModule {
+ @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepository
+
+ @Binds
+ @IntoMap
+ @ClassKey(StatusBarModeRepositoryImpl::class)
+ fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
+
+ @Binds
+ @IntoSet
+ fun bindViewInitListener(impl: StatusBarModeRepositoryImpl): OnStatusBarViewInitializedListener
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
new file mode 100644
index 000000000000..1248b1c1cbb0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/SilentNotificationStatusIconsVisibilityInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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.domain.interactor
+
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import javax.inject.Inject
+
+class SilentNotificationStatusIconsVisibilityInteractor
+@Inject
+constructor(private val repository: NotificationListenerSettingsRepository) {
+ /** Set whether icons for silent notifications be hidden in the status bar. */
+ fun setHideSilentStatusIcons(hideIcons: Boolean) {
+ repository.showSilentStatusIcons.value = !hideIcons
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
index bde298d7a33d..ea1d7820c79c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt
@@ -36,6 +36,11 @@ interface StatusEvent {
val showAnimation: Boolean
val viewCreator: ViewCreator
var contentDescription: String?
+ /**
+ * When true, an accessibility event with [contentDescription] is announced when the view
+ * becomes visible.
+ */
+ val shouldAnnounceAccessibilityEvent: Boolean
// Update this event with values from another event.
fun updateFromEvent(other: StatusEvent?) {
@@ -76,6 +81,7 @@ class BatteryEvent(@IntRange(from = 0, to = 100) val batteryLevel: Int) : Status
override var forceVisible = false
override val showAnimation = true
override var contentDescription: String? = ""
+ override val shouldAnnounceAccessibilityEvent: Boolean = false
override val viewCreator: ViewCreator = { context ->
BatteryStatusChip(context).apply {
@@ -95,6 +101,7 @@ class ConnectedDisplayEvent : StatusEvent {
override var forceVisible = false
override val showAnimation = true
override var contentDescription: String? = ""
+ override val shouldAnnounceAccessibilityEvent: Boolean = true
override val viewCreator: ViewCreator = { context ->
ConnectedDisplayChip(context)
@@ -110,6 +117,7 @@ open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEven
override var contentDescription: String? = null
override val priority = 100
override var forceVisible = true
+ override val shouldAnnounceAccessibilityEvent: Boolean = false
var privacyItems: List<PrivacyItem> = listOf()
private var privacyChip: OngoingPrivacyChip? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index dccc23f61092..73c0bfec69a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -273,6 +273,11 @@ class SystemEventChipAnimationController @Inject constructor(
})
}
+ /** Announces [contentDescriptions] for accessibility. */
+ fun announceForAccessibility(contentDescriptions: String) {
+ currentAnimatedView?.view?.announceForAccessibility(contentDescriptions)
+ }
+
private fun updateDimens(contentArea: Rect) {
val lp = animationWindowView.layoutParams as FrameLayout.LayoutParams
lp.height = contentArea.height()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 7d866df73da9..8ee1ade7a185 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -100,9 +100,12 @@ constructor(
}
private fun startConnectedDisplayCollection() {
+ val connectedDisplayEvent = ConnectedDisplayEvent().apply {
+ contentDescription = context.getString(R.string.connected_display_icon_desc)
+ }
connectedDisplayCollectionJob =
onDisplayConnectedFlow
- .onEach { scheduler.onStatusEvent(ConnectedDisplayEvent()) }
+ .onEach { scheduler.onStatusEvent(connectedDisplayEvent) }
.launchIn(appScope)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index a3bc00248362..f0e60dd2ce54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -254,11 +254,18 @@ constructor(
currentlyRunningAnimationJob =
coroutineScope.launch {
runChipAppearAnimation()
+ announceForAccessibilityIfNeeded(event)
delay(APPEAR_ANIMATION_DURATION + DISPLAY_LENGTH)
runChipDisappearAnimation()
}
}
+ private fun announceForAccessibilityIfNeeded(event: StatusEvent) {
+ val description = event.contentDescription ?: return
+ if (!event.shouldAnnounceAccessibilityEvent) return
+ chipAnimationController.announceForAccessibility(description)
+ }
+
/**
* 1. Define a total budget for the chip animation (1500ms)
* 2. Send out callbacks to listeners so that they can generate animations locally
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 756151bd57e0..96279e2d2e44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -21,7 +21,7 @@ import android.view.ViewGroup
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.LaunchAnimator
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -33,7 +33,7 @@ private const val TAG = "NotificationLaunchAnimatorController"
/** A provider of [NotificationLaunchAnimatorController]. */
class NotificationLaunchAnimatorControllerProvider(
- private val notificationExpansionRepository: NotificationExpansionRepository,
+ private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
private val notificationListContainer: NotificationListContainer,
private val headsUpManager: HeadsUpManager,
private val jankMonitor: InteractionJankMonitor
@@ -44,7 +44,7 @@ class NotificationLaunchAnimatorControllerProvider(
onFinishAnimationCallback: Runnable? = null
): NotificationLaunchAnimatorController {
return NotificationLaunchAnimatorController(
- notificationExpansionRepository,
+ notificationLaunchAnimationInteractor,
notificationListContainer,
headsUpManager,
notification,
@@ -60,7 +60,7 @@ class NotificationLaunchAnimatorControllerProvider(
* notification expanding into an opening window.
*/
class NotificationLaunchAnimatorController(
- private val notificationExpansionRepository: NotificationExpansionRepository,
+ private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
private val notificationListContainer: NotificationListContainer,
private val headsUpManager: HeadsUpManager,
private val notification: ExpandableNotificationRow,
@@ -143,7 +143,7 @@ class NotificationLaunchAnimatorController(
if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
}
- notificationExpansionRepository.setIsExpandAnimationRunning(willAnimate)
+ notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate)
notificationEntry.isExpandAnimationRunning = willAnimate
if (!willAnimate) {
@@ -180,7 +180,7 @@ class NotificationLaunchAnimatorController(
// TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
// here?
- notificationExpansionRepository.setIsExpandAnimationRunning(false)
+ notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
notificationEntry.isExpandAnimationRunning = false
removeHun(animate = true)
onFinishAnimationCallback?.run()
@@ -200,7 +200,7 @@ class NotificationLaunchAnimatorController(
jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
notification.isExpandAnimationRunning = false
- notificationExpansionRepository.setIsExpandAnimationRunning(false)
+ notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
notificationEntry.isExpandAnimationRunning = false
notificationListContainer.setExpandingNotification(null)
applyParams(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 57d20246ea14..f98f39e3532c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -20,6 +20,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.util.Log;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -33,6 +34,7 @@ import com.android.systemui.statusbar.notification.stack.ViewState;
* An animator to animate properties
*/
public class PropertyAnimator {
+ private static final String TAG = "PropertyAnimator";
/**
* Set a property on a view, updating its value, even if it's already animating.
@@ -114,18 +116,23 @@ public class PropertyAnimator {
|| previousAnimator.getAnimatedFraction() == 0)) {
animator.setStartDelay(properties.delay);
}
- if (listener != null) {
- animator.addListener(listener);
- }
// remove the tag when the animation is finished
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setTag(animatorTag, null);
- view.setTag(animationStartTag, null);
- view.setTag(animationEndTag, null);
+ Animator existing = (Animator) view.getTag(animatorTag);
+ if (existing == animation) {
+ view.setTag(animatorTag, null);
+ view.setTag(animationStartTag, null);
+ view.setTag(animationEndTag, null);
+ } else {
+ Log.e(TAG, "Unexpected Animator set during onAnimationEnd. Not cleaning up.");
+ }
}
});
+ if (listener != null) {
+ animator.addListener(listener);
+ }
ViewState.startAnimator(animator, listener);
view.setTag(animatorTag, animator);
view.setTag(animationStartTag, currentValue);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
index ff89c62ab496..23f87ba9dcc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt
@@ -83,6 +83,21 @@ constructor(@NotificationRemoteInputLog private val logBuffer: LogBuffer) {
}
)
+ fun logRemoteInputApplySkipped(entryKey: String, reason: String, notificationStyle: String) =
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = entryKey
+ str2 = reason
+ str3 = notificationStyle
+ },
+ {
+ "removeRemoteInput[apply is skipped] reason: $str2" +
+ "for entry: $str1, style: $str3 "
+ }
+ )
+
private companion object {
private const val TAG = "RemoteInputControllerLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index e763797d9966..7b3a93a4a094 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -225,7 +225,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
/** @see NotifPipeline#getEntry(String) () */
@Nullable
- NotificationEntry getEntry(@NonNull String key) {
+ public NotificationEntry getEntry(@NonNull String key) {
return mNotificationSet.get(key);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4573d5989faa..cfe9fbe3af29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -748,7 +748,11 @@ public final class NotificationEntry extends ListEntry {
return row != null && row.getGuts() != null && row.getGuts().isExposed();
}
- public boolean isChildInGroup() {
+ /**
+ * @return Whether the notification row is a child of a group notification view; false if the
+ * row is null
+ */
+ public boolean rowIsChildInGroup() {
return row != null && row.isChildInGroup();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2d839704e0b7..0c69a65b96af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -37,8 +37,8 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -85,7 +85,7 @@ constructor(
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
private val secureSettings: SecureSettings,
- private val notificationListInteractor: NotificationListInteractor,
+ private val seenNotificationsInteractor: SeenNotificationsInteractor,
private val statusBarStateController: StatusBarStateController,
) : Coordinator, Dumpable {
@@ -351,7 +351,7 @@ constructor(
override fun onCleanup() {
logger.logProviderHasFilteredOutSeenNotifs(hasFilteredAnyNotifs)
- notificationListInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
+ seenNotificationsInteractor.setHasFilteredOutSeenNotifications(hasFilteredAnyNotifs)
hasFilteredAnyNotifs = false
}
}
@@ -389,7 +389,7 @@ constructor(
with(pw.asIndenting()) {
println(
"notificationListInteractor.hasFilteredOutSeenNotifications.value=" +
- notificationListInteractor.hasFilteredOutSeenNotifications.value
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.value
)
println("unseen notifications:")
indentIfPossible {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 62a0d138fd05..07e84bb37fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -16,25 +16,32 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.util.traceSection
import javax.inject.Inject
/**
- * A small coordinator which updates the notif stack (the view layer which holds notifications)
- * with high-level data after the stack is populated with the final entries.
+ * A small coordinator which updates the notif stack (the view layer which holds notifications) with
+ * high-level data after the stack is populated with the final entries.
*/
@CoordinatorScope
-class StackCoordinator @Inject internal constructor(
+class StackCoordinator
+@Inject
+internal constructor(
+ private val featureFlags: FeatureFlagsClassic,
private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
- private val notificationIconAreaController: NotificationIconAreaController
+ private val notificationIconAreaController: NotificationIconAreaController,
+ private val renderListInteractor: RenderNotificationListInteractor,
) : Coordinator {
override fun attach(pipeline: NotifPipeline) {
@@ -45,7 +52,11 @@ class StackCoordinator @Inject internal constructor(
fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
traceSection("StackCoordinator.onAfterRenderList") {
controller.setNotifStats(calculateNotifStats(entries))
- notificationIconAreaController.updateNotificationIcons(entries)
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ renderListInteractor.setRenderedList(entries)
+ } else {
+ notificationIconAreaController.updateNotificationIcons(entries)
+ }
}
private fun calculateNotifStats(entries: List<ListEntry>): NotifStats {
@@ -75,4 +86,4 @@ class StackCoordinator @Inject internal constructor(
hasClearableSilentNotifs = hasClearableSilentNotifs
)
}
-} \ No newline at end of file
+}
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 8561869af352..fa366c65b4a3 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
@@ -54,7 +54,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
@@ -204,12 +204,12 @@ public interface NotificationsModule {
@Provides
@SysUISingleton
static NotificationLaunchAnimatorControllerProvider provideNotifLaunchAnimControllerProvider(
- NotificationExpansionRepository notificationExpansionRepository,
+ NotificationLaunchAnimationInteractor notificationLaunchAnimationInteractor,
NotificationListContainer notificationListContainer,
HeadsUpManager headsUpManager,
InteractionJankMonitor jankMonitor) {
return new NotificationLaunchAnimatorControllerProvider(
- notificationExpansionRepository,
+ notificationLaunchAnimationInteractor,
notificationListContainer,
headsUpManager,
jankMonitor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
new file mode 100644
index 000000000000..8064f049c88e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Repository of "active" notifications in the notification list.
+ *
+ * This repository serves as the boundary between the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] and the modern
+ * notifications presentation codebase.
+ */
+@SysUISingleton
+class ActiveNotificationListRepository @Inject constructor() {
+ /**
+ * Notifications actively presented to the user in the notification stack.
+ *
+ * @see com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+ */
+ val activeNotifications = MutableStateFlow(emptyMap<String, ActiveNotificationModel>())
+
+ /** Are any already-seen notifications currently filtered out of the active list? */
+ val hasFilteredOutSeenNotifications = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt
new file mode 100644
index 000000000000..afed6bef58fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpNotificationIconViewStateRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import android.graphics.Rect
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** View-states pertaining to heads-up notification icons. */
+@SysUISingleton
+class HeadsUpNotificationIconViewStateRepository @Inject constructor() {
+ /** Notification key for a notification icon to show isolated, or `null` if none. */
+ val isolatedNotification = MutableStateFlow<String?>(null)
+ /** Area to display the isolated notification, or `null` if none. */
+ val isolatedIconLocation = MutableStateFlow<Rect?>(null)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt
new file mode 100644
index 000000000000..9b562991f9a3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationLaunchAnimationRepository.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** A repository tracking the status of notification launch animations. */
+@SysUISingleton
+class NotificationLaunchAnimationRepository @Inject constructor() {
+ val isLaunchAnimationRunning = MutableStateFlow(false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
new file mode 100644
index 000000000000..bfec60bcd6db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class ActiveNotificationsInteractor
+@Inject
+constructor(
+ repository: ActiveNotificationListRepository,
+) {
+ /** Notifications actively presented to the user in the notification stack, in order. */
+ val notifications: Flow<Collection<ActiveNotificationModel>> =
+ repository.activeNotifications.map { it.values }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
new file mode 100644
index 000000000000..17b6e9f572c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationIconInteractor.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Domain logic pertaining to heads up notification icons. */
+class HeadsUpNotificationIconInteractor
+@Inject
+constructor(
+ private val repository: HeadsUpNotificationIconViewStateRepository,
+) {
+ /** Notification key for a notification icon to show isolated, or `null` if none. */
+ val isolatedIconLocation: Flow<Rect?> = repository.isolatedIconLocation
+
+ /** Area to display the isolated notification, or `null` if none. */
+ val isolatedNotification: Flow<String?> = repository.isolatedNotification
+
+ /** Updates the location where isolated notification icons are shown. */
+ fun setIsolatedIconLocation(rect: Rect?) {
+ repository.isolatedIconLocation.value = rect
+ }
+
+ /** Updates which notification will have its icon displayed isolated. */
+ fun setIsolatedIconNotificationKey(key: String?) {
+ repository.isolatedNotification.value = key
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
index 8f7e269d34b0..8079ce540e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractor.kt
@@ -20,15 +20,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
import javax.inject.Inject
-/** Interactor for notifications in general. */
+/** Interactor for notification alerting. */
@SysUISingleton
-class NotificationsInteractor
+class NotificationAlertsInteractor
@Inject
constructor(
private val disableFlagsRepository: DisableFlagsRepository,
) {
/** Returns true if notification alerts are allowed. */
- fun areNotificationAlertsEnabled(): Boolean {
- return disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
- }
+ fun areNotificationAlertsEnabled(): Boolean =
+ disableFlagsRepository.disableFlags.value.areNotificationAlertsEnabled()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
index 6f0a97adb311..22ce4f11b661 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractor.kt
@@ -14,22 +14,20 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.data.repository
+package com.android.systemui.statusbar.notification.domain.interactor
import android.util.Log
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
-
-private const val TAG = "NotificationExpansionRepository"
+import kotlinx.coroutines.flow.StateFlow
/** A repository tracking the status of notification expansion animations. */
@SysUISingleton
-class NotificationExpansionRepository @Inject constructor() {
- private val _isExpandAnimationRunning = MutableStateFlow(false)
+class NotificationLaunchAnimationInteractor
+@Inject
+constructor(private val repository: NotificationLaunchAnimationRepository) {
/**
* Emits true if an animation that expands a notification object into an opening window is
@@ -37,13 +35,18 @@ class NotificationExpansionRepository @Inject constructor() {
*
* See [com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController].
*/
- val isExpandAnimationRunning: Flow<Boolean> = _isExpandAnimationRunning.asStateFlow()
+ val isLaunchAnimationRunning: StateFlow<Boolean>
+ get() = repository.isLaunchAnimationRunning
- /** Sets whether the notification expansion animation is currently running. */
- fun setIsExpandAnimationRunning(running: Boolean) {
+ /** Sets whether the notification expansion launch animation is currently running. */
+ fun setIsLaunchAnimationRunning(running: Boolean) {
if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
- Log.d(TAG, "setIsExpandAnimationRunning(running=$running)")
+ Log.d(TAG, "setIsLaunchAnimationRunning(running=$running)")
}
- _isExpandAnimationRunning.value = running
+ repository.isLaunchAnimationRunning.value = running
+ }
+
+ companion object {
+ private const val TAG = "NotificationLaunchAnimationInteractor"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
new file mode 100644
index 000000000000..604ecbc047ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.graphics.drawable.Icon
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.update
+
+private typealias ModelStore = Map<String, ActiveNotificationModel>
+
+/**
+ * Logic for passing information from the
+ * [com.android.systemui.statusbar.notification.collection.NotifPipeline] to the presentation
+ * layers.
+ */
+class RenderNotificationListInteractor
+@Inject
+constructor(
+ private val repository: ActiveNotificationListRepository,
+ private val sectionStyleProvider: SectionStyleProvider,
+) {
+ /**
+ * Sets the current list of rendered notification entries as displayed in the notification
+ * stack.
+ *
+ * @see com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository.activeNotifications
+ */
+ fun setRenderedList(entries: List<ListEntry>) {
+ repository.activeNotifications.update { existingModels ->
+ entries.associateBy(
+ keySelector = { it.key },
+ valueTransform = { it.toModel(existingModels) },
+ )
+ }
+ }
+
+ private fun ListEntry.toModel(
+ existingModels: ModelStore,
+ ): ActiveNotificationModel =
+ existingModels.createOrReuse(
+ key = key,
+ groupKey = representativeEntry?.sbn?.groupKey,
+ isAmbient = sectionStyleProvider.isMinimized(this),
+ isRowDismissed = representativeEntry?.isRowDismissed == true,
+ isSilent = sectionStyleProvider.isSilent(this),
+ isLastMessageFromReply = representativeEntry?.isLastMessageFromReply == true,
+ isSuppressedFromStatusBar = representativeEntry?.shouldSuppressStatusBar() == true,
+ isPulsing = representativeEntry?.showingPulsing() == true,
+ aodIcon = representativeEntry?.icons?.aodIcon?.sourceIcon,
+ shelfIcon = representativeEntry?.icons?.shelfIcon?.sourceIcon,
+ statusBarIcon = representativeEntry?.icons?.statusBarIcon?.sourceIcon,
+ )
+
+ private fun ModelStore.createOrReuse(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+ ): ActiveNotificationModel {
+ return this[key]?.takeIf {
+ it.isCurrent(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon
+ )
+ }
+ ?: ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
+ }
+
+ private fun ActiveNotificationModel.isCurrent(
+ key: String,
+ groupKey: String?,
+ isAmbient: Boolean,
+ isRowDismissed: Boolean,
+ isSilent: Boolean,
+ isLastMessageFromReply: Boolean,
+ isSuppressedFromStatusBar: Boolean,
+ isPulsing: Boolean,
+ aodIcon: Icon?,
+ shelfIcon: Icon?,
+ statusBarIcon: Icon?
+ ): Boolean {
+ return when {
+ key != this.key -> false
+ groupKey != this.groupKey -> false
+ isAmbient != this.isAmbient -> false
+ isRowDismissed != this.isRowDismissed -> false
+ isSilent != this.isSilent -> false
+ isLastMessageFromReply != this.isLastMessageFromReply -> false
+ isSuppressedFromStatusBar != this.isSuppressedFromStatusBar -> false
+ isPulsing != this.isPulsing -> false
+ aodIcon != this.aodIcon -> false
+ shelfIcon != this.shelfIcon -> false
+ statusBarIcon != this.statusBarIcon -> false
+ else -> true
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 3fd68a8bdd7c..1f7ab962c3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -14,25 +14,26 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.stack.domain.interactor
+package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
/** Interactor for business logic associated with the notification stack. */
@SysUISingleton
-class NotificationListInteractor
+class SeenNotificationsInteractor
@Inject
constructor(
- private val notificationListRepository: NotificationListRepository,
+ private val notificationListRepository: ActiveNotificationListRepository,
) {
/** Are any already-seen notifications currently filtered out of the shade? */
- val hasFilteredOutSeenNotifications: StateFlow<Boolean>
- get() = notificationListRepository.hasFilteredOutSeenNotifications
+ val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
+ notificationListRepository.hasFilteredOutSeenNotifications
+ /** Set whether already-seen notifications are currently filtered out of the shade. */
fun setHasFilteredOutSeenNotifications(value: Boolean) {
- notificationListRepository.setHasFilteredOutSeenNotifications(value)
+ notificationListRepository.hasFilteredOutSeenNotifications.value = value
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
new file mode 100644
index 000000000000..94e70e5521c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the FooterView refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object FooterViewRefactor {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationsFooterViewRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index e74b3fcdf050..ba916542fa67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -19,6 +19,9 @@ package com.android.systemui.statusbar.notification.footer.ui.view;
import static android.graphics.PorterDuff.Mode.SRC_ATOP;
import android.annotation.ColorInt;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -35,6 +38,7 @@ import androidx.annotation.NonNull;
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
@@ -44,6 +48,8 @@ import com.android.systemui.util.DumpUtilsKt;
import java.io.PrintWriter;
public class FooterView extends StackScrollerDecorView {
+ private static final String TAG = "FooterView";
+
private FooterViewButton mClearAllButton;
private FooterViewButton mManageButton;
private boolean mShowHistory;
@@ -57,6 +63,9 @@ public class FooterView extends StackScrollerDecorView {
private String mSeenNotifsFilteredText;
private Drawable mSeenNotifsFilteredIcon;
+ private @StringRes int mMessageStringId;
+ private @DrawableRes int mMessageIconId;
+
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -84,6 +93,50 @@ public class FooterView extends StackScrollerDecorView {
});
}
+ /** Set the string for a message to be shown instead of the buttons. */
+ public void setMessageString(@StringRes int messageId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mMessageStringId == messageId) {
+ return; // nothing changed
+ }
+ mMessageStringId = messageId;
+ updateMessageString();
+ }
+
+ private void updateMessageString() {
+ if (mMessageStringId == 0) {
+ return; // not initialized yet
+ }
+ String messageString = getContext().getString(mMessageStringId);
+ mSeenNotifsFooterTextView.setText(messageString);
+ }
+
+
+ /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
+ public void setMessageIcon(@DrawableRes int iconId) {
+ if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
+ if (mMessageIconId == iconId) {
+ return; // nothing changed
+ }
+ mMessageIconId = iconId;
+ updateMessageIcon();
+ }
+
+ private void updateMessageIcon() {
+ if (mMessageIconId == 0) {
+ return; // not initialized yet
+ }
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ @SuppressLint("UseCompatLoadingForDrawables")
+ Drawable messageIcon = getContext().getDrawable(mMessageIconId);
+ if (messageIcon != null) {
+ messageIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ mSeenNotifsFooterTextView
+ .setCompoundDrawablesRelative(messageIcon, null, null, null);
+ }
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -148,9 +201,11 @@ public class FooterView extends StackScrollerDecorView {
mManageButton.setText(mManageNotificationText);
mManageButton.setContentDescription(mManageNotificationText);
}
- mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
- mSeenNotifsFooterTextView
- .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+ if (!FooterViewRefactor.isEnabled()) {
+ mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
+ mSeenNotifsFooterTextView
+ .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+ }
}
/** Whether the start button shows "History" (true) or "Manage" (false). */
@@ -167,6 +222,11 @@ public class FooterView extends StackScrollerDecorView {
mContext.getString(R.string.accessibility_clear_all));
updateResources();
updateContent();
+
+ if (FooterViewRefactor.isEnabled()) {
+ updateMessageString();
+ updateMessageIcon();
+ }
}
/**
@@ -200,11 +260,13 @@ public class FooterView extends StackScrollerDecorView {
mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
mManageNotificationHistoryText = getContext()
.getString(R.string.manage_notifications_history_text);
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ if (!FooterViewRefactor.isEnabled()) {
+ int unlockIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
+ mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
+ mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
+ mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
new file mode 100644
index 000000000000..6d8234371b65
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewbinder
+
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.launch
+
+/** Binds a [FooterView] to its [view model][FooterViewModel]. */
+object FooterViewBinder {
+ fun bind(
+ footer: FooterView,
+ viewModel: FooterViewModel,
+ ): DisposableHandle {
+ return footer.repeatWhenAttached {
+ // Listen for changes when the view is attached.
+ lifecycleScope.launch {
+ viewModel.message.collect { message ->
+ footer.setFooterLabelVisible(message.visible)
+ footer.setMessageString(message.messageId)
+ footer.setMessageIcon(message.iconId)
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
new file mode 100644
index 000000000000..bc912fb106f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterMessageViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.StringRes
+
+/** A ViewModel for the string message that can be shown in the footer. */
+data class FooterMessageViewModel(
+ @StringRes val messageId: Int,
+ @DrawableRes val iconId: Int,
+ val visible: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
new file mode 100644
index 000000000000..ffa5ff052454
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [FooterView]. */
+@SysUISingleton
+class FooterViewModel
+@Inject
+constructor(seenNotificationsInteractor: SeenNotificationsInteractor) {
+ init {
+ /* Check if */ FooterViewRefactor.isUnexpectedlyInLegacyMode()
+ }
+
+ val message: Flow<FooterMessageViewModel> =
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.map { hasFilteredOutNotifs ->
+ FooterMessageViewModel(
+ messageId = R.string.unlock_to_see_notif_text,
+ iconId = R.drawable.ic_friction_lock_closed,
+ visible = hasFilteredOutNotifs,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
new file mode 100644
index 000000000000..00d873e074b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.icon.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.wm.shell.bubbles.Bubbles
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.jvm.optionals.getOrNull
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+
+/** Domain logic related to notification icons. */
+class NotificationIconsInteractor
+@Inject
+constructor(
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ private val bubbles: Optional<Bubbles>,
+ private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
+) {
+ /** Returns a subset of all active notifications based on the supplied filtration parameters. */
+ fun filteredNotifSet(
+ showAmbient: Boolean = true,
+ showLowPriority: Boolean = true,
+ showDismissed: Boolean = true,
+ showRepliedMessages: Boolean = true,
+ showPulsing: Boolean = true,
+ ): Flow<Set<ActiveNotificationModel>> {
+ return combine(
+ activeNotificationsInteractor.notifications,
+ keyguardViewStateRepository.areNotificationsFullyHidden,
+ ) { notifications, notifsFullyHidden ->
+ notifications
+ .asSequence()
+ .filter { model: ActiveNotificationModel ->
+ shouldShowNotificationIcon(
+ model = model,
+ showAmbient = showAmbient,
+ showLowPriority = showLowPriority,
+ showDismissed = showDismissed,
+ showRepliedMessages = showRepliedMessages,
+ showPulsing = showPulsing,
+ notifsFullyHidden = notifsFullyHidden,
+ )
+ }
+ .toSet()
+ }
+ }
+
+ private fun shouldShowNotificationIcon(
+ model: ActiveNotificationModel,
+ showAmbient: Boolean,
+ showLowPriority: Boolean,
+ showDismissed: Boolean,
+ showRepliedMessages: Boolean,
+ showPulsing: Boolean,
+ notifsFullyHidden: Boolean,
+ ): Boolean {
+ return when {
+ !showAmbient && model.isAmbient -> false
+ !showLowPriority && model.isSilent -> false
+ !showDismissed && model.isRowDismissed -> false
+ !showRepliedMessages && model.isLastMessageFromReply -> false
+ !showAmbient && model.isSuppressedFromStatusBar -> false
+ !showPulsing && model.isPulsing && !notifsFullyHidden -> false
+ bubbles.getOrNull()?.isBubbleExpanded(model.key) == true -> false
+ else -> true
+ }
+ }
+}
+
+/** Domain logic related to notification icons shown on the always-on display. */
+class AlwaysOnDisplayNotificationIconsInteractor
+@Inject
+constructor(
+ deviceEntryInteractor: DeviceEntryInteractor,
+ iconsInteractor: NotificationIconsInteractor,
+) {
+ val aodNotifs: Flow<Set<ActiveNotificationModel>> =
+ deviceEntryInteractor.isBypassEnabled.flatMapLatest { isBypassEnabled ->
+ iconsInteractor.filteredNotifSet(
+ showAmbient = false,
+ showDismissed = false,
+ showRepliedMessages = false,
+ showPulsing = !isBypassEnabled,
+ )
+ }
+}
+
+/** Domain logic related to notification icons shown in the status bar. */
+class StatusBarNotificationIconsInteractor
+@Inject
+constructor(
+ iconsInteractor: NotificationIconsInteractor,
+ settingsRepository: NotificationListenerSettingsRepository,
+) {
+ val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
+ settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
+ iconsInteractor.filteredNotifSet(
+ showAmbient = false,
+ showLowPriority = showSilentIcons,
+ showDismissed = false,
+ showRepliedMessages = false,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
index eb5c1fa3b0ca..246933ad69e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImpl.kt
@@ -16,50 +16,24 @@
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
import android.content.Context
-import android.graphics.Color
import android.graphics.Rect
-import android.os.Bundle
-import android.os.Trace
-import android.view.LayoutInflater
import android.view.View
-import android.widget.FrameLayout
-import androidx.annotation.ColorInt
-import androidx.annotation.VisibleForTesting
-import androidx.collection.ArrayMap
-import com.android.internal.statusbar.StatusBarIcon
-import com.android.internal.util.ContrastColorUtil
-import com.android.settingslib.Utils
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.demomode.DemoMode
-import com.android.systemui.demomode.DemoModeController
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.flags.RefactorFlag
-import com.android.systemui.plugins.DarkIconDispatcher
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.NotificationListener
-import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShelfController
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.NotificationUtils
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.ListEntry
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerShelfViewModel
-import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl
import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.NotificationIconAreaController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.window.StatusBarWindowController
-import com.android.wm.shell.bubbles.Bubbles
-import java.util.Optional
-import java.util.function.Function
import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
@@ -74,69 +48,22 @@ import kotlinx.coroutines.DisposableHandle
class NotificationIconAreaControllerViewBinderWrapperImpl
@Inject
constructor(
- private val context: Context,
- private val wakeUpCoordinator: NotificationWakeUpCoordinator,
- private val bypassController: KeyguardBypassController,
+ private val configuration: ConfigurationState,
private val configurationController: ConfigurationController,
- private val mediaManager: NotificationMediaManager,
- notificationListener: NotificationListener,
private val dozeParameters: DozeParameters,
- private val sectionStyleProvider: SectionStyleProvider,
- private val bubblesOptional: Optional<Bubbles>,
- demoModeController: DemoModeController,
- darkIconDispatcher: DarkIconDispatcher,
private val featureFlags: FeatureFlagsClassic,
- private val statusBarWindowController: StatusBarWindowController,
private val screenOffAnimationController: ScreenOffAnimationController,
+ private val shelfIconViewStore: ShelfNotificationIconViewStore,
private val shelfIconsViewModel: NotificationIconContainerShelfViewModel,
- private val statusBarIconsViewModel: NotificationIconContainerStatusBarViewModel,
+ private val aodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
private val aodIconsViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
-) :
- NotificationIconAreaController,
- DarkIconDispatcher.DarkReceiver,
- NotificationWakeUpCoordinator.WakeUpListener,
- DemoMode {
+) : NotificationIconAreaController {
- private val contrastColorUtil: ContrastColorUtil = ContrastColorUtil.getInstance(context)
- private val updateStatusBarIcons = Runnable { updateStatusBarIcons() }
private val shelfRefactor = RefactorFlag(featureFlags, Flags.NOTIFICATION_SHELF_REFACTOR)
- private val tintAreas = ArrayList<Rect>()
- private var iconSize = 0
- private var iconHPadding = 0
- private var iconTint = Color.WHITE
- private var notificationEntries = listOf<ListEntry>()
- private var notificationIconArea: View? = null
- private var notificationIcons: NotificationIconContainer? = null
private var shelfIcons: NotificationIconContainer? = null
private var aodIcons: NotificationIconContainer? = null
private var aodBindJob: DisposableHandle? = null
- private var aodIconAppearTranslation = 0
- private var aodIconTint = 0
- private var showLowPriority = true
-
- @VisibleForTesting
- val settingsListener: NotificationListener.NotificationSettingsListener =
- object : NotificationListener.NotificationSettingsListener {
- override fun onStatusBarIconsBehaviorChanged(hideSilentStatusIcons: Boolean) {
- showLowPriority = !hideSilentStatusIcons
- updateStatusBarIcons()
- }
- }
-
- init {
- wakeUpCoordinator.addListener(this)
- demoModeController.addCallback(this)
- notificationListener.addNotificationSettingsListener(settingsListener)
- initializeNotificationAreaViews(context)
- reloadAodColor()
- darkIconDispatcher.addDarkReceiver(this)
- }
-
- @VisibleForTesting
- fun shouldShowLowPriorityIcons(): Boolean {
- return showLowPriority
- }
/** Called by the Keyguard*ViewController whose view contains the aod icons. */
override fun setupAodIcons(aodIcons: NotificationIconContainer) {
@@ -152,429 +79,58 @@ constructor(
NotificationIconContainerViewBinder.bind(
aodIcons,
aodIconsViewModel,
+ configuration,
configurationController,
dozeParameters,
featureFlags,
screenOffAnimationController,
+ aodIconViewStore,
)
- if (changed) {
- updateAodNotificationIcons()
- }
- updateIconLayoutParams(context)
}
override fun setupShelf(notificationShelfController: NotificationShelfController) =
NotificationShelfViewBinderWrapperControllerImpl.unsupported
override fun setShelfIcons(icons: NotificationIconContainer) {
- if (shelfRefactor.isUnexpectedlyInLegacyMode()) return
- NotificationIconContainerViewBinder.bind(
- icons,
- shelfIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- shelfIcons = icons
+ if (shelfRefactor.isUnexpectedlyInLegacyMode()) {
+ NotificationIconContainerViewBinder.bind(
+ icons,
+ shelfIconsViewModel,
+ configuration,
+ configurationController,
+ dozeParameters,
+ featureFlags,
+ screenOffAnimationController,
+ shelfIconViewStore,
+ )
+ shelfIcons = icons
+ }
}
- override fun onDensityOrFontScaleChanged(context: Context) {
- updateIconLayoutParams(context)
- }
+ override fun onDensityOrFontScaleChanged(context: Context) = unsupported
/** Returns the view that represents the notification area. */
- override fun getNotificationInnerAreaView(): View? {
- return notificationIconArea
- }
-
- /**
- * See [com.android.systemui.statusbar.policy.DarkIconDispatcher.setIconsDarkArea]. Sets the
- * color that should be used to tint any icons in the notification area.
- *
- * @param tintAreas the areas in which to tint the icons, specified in screen coordinates
- * @param darkIntensity
- */
- override fun onDarkChanged(tintAreas: ArrayList<Rect>, darkIntensity: Float, iconTint: Int) {
- this.tintAreas.clear()
- this.tintAreas.addAll(tintAreas)
- if (DarkIconDispatcher.isInAreas(tintAreas, notificationIconArea)) {
- this.iconTint = iconTint
- }
- applyNotificationIconsTint()
- }
+ override fun getNotificationInnerAreaView(): View? = unsupported
/** Updates the notifications with the given list of notifications to display. */
- override fun updateNotificationIcons(entries: List<ListEntry>) {
- notificationEntries = entries
- updateNotificationIcons()
- }
-
- private fun updateStatusBarIcons() {
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.statusBarIcon },
- notificationIcons,
- showAmbient = false /* showAmbient */,
- showLowPriority = showLowPriority,
- hideDismissed = true /* hideDismissed */,
- hideRepliedMessages = true /* hideRepliedMessages */,
- hideCurrentMedia = false /* hideCurrentMedia */,
- hidePulsing = false /* hidePulsing */
- )
- }
+ override fun updateNotificationIcons(entries: List<ListEntry>) = unsupported
- override fun updateAodNotificationIcons() {
- if (aodIcons == null) {
- return
- }
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.aodIcon },
- aodIcons,
- showAmbient = false /* showAmbient */,
- showLowPriority = true /* showLowPriority */,
- hideDismissed = true /* hideDismissed */,
- hideRepliedMessages = true /* hideRepliedMessages */,
- hideCurrentMedia = true /* hideCurrentMedia */,
- hidePulsing = bypassController.bypassEnabled /* hidePulsing */
- )
- }
+ override fun updateAodNotificationIcons() = unsupported
- override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) {
- notificationIcons!!.showIconIsolated(icon, animated)
- }
+ override fun showIconIsolated(icon: StatusBarIconView?, animated: Boolean) = unsupported
- override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) {
- notificationIcons!!.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate)
- }
+ override fun setIsolatedIconLocation(iconDrawingRect: Rect, requireStateUpdate: Boolean) =
+ unsupported
override fun setAnimationsEnabled(enabled: Boolean) = unsupported
- override fun onThemeChanged() {
- reloadAodColor()
- updateAodIconColors()
- }
+ override fun onThemeChanged() = unsupported
override fun getHeight(): Int {
return if (aodIcons == null) 0 else aodIcons!!.height
}
- override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
- updateAodNotificationIcons()
- updateAodIconColors()
- }
-
- override fun demoCommands(): List<String> {
- val commands = ArrayList<String>()
- commands.add(DemoMode.COMMAND_NOTIFICATIONS)
- return commands
- }
-
- override fun dispatchDemoCommand(command: String, args: Bundle) {
- if (notificationIconArea != null) {
- val visible = args.getString("visible")
- val vis = if ("false" == visible) View.INVISIBLE else View.VISIBLE
- notificationIconArea?.visibility = vis
- }
- }
-
- override fun onDemoModeFinished() {
- if (notificationIconArea != null) {
- notificationIconArea?.visibility = View.VISIBLE
- }
- }
-
- private fun inflateIconArea(inflater: LayoutInflater): View {
- return inflater.inflate(R.layout.notification_icon_area, null)
- }
-
- /** Initializes the views that will represent the notification area. */
- private fun initializeNotificationAreaViews(context: Context) {
- reloadDimens(context)
- val layoutInflater = LayoutInflater.from(context)
- notificationIconArea = inflateIconArea(layoutInflater)
- notificationIcons = notificationIconArea?.findViewById(R.id.notificationIcons)
- NotificationIconContainerViewBinder.bind(
- notificationIcons!!,
- statusBarIconsViewModel,
- configurationController,
- dozeParameters,
- featureFlags,
- screenOffAnimationController,
- )
- }
-
- private fun updateIconLayoutParams(context: Context) {
- reloadDimens(context)
- val params = generateIconLayoutParams()
- for (i in 0 until notificationIcons!!.childCount) {
- val child = notificationIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- if (shelfIcons != null) {
- for (i in 0 until shelfIcons!!.childCount) {
- val child = shelfIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- }
- if (aodIcons != null) {
- for (i in 0 until aodIcons!!.childCount) {
- val child = aodIcons!!.getChildAt(i)
- child.layoutParams = params
- }
- }
- }
-
- private fun generateIconLayoutParams(): FrameLayout.LayoutParams {
- return FrameLayout.LayoutParams(
- iconSize + 2 * iconHPadding,
- statusBarWindowController.statusBarHeight
- )
- }
-
- private fun reloadDimens(context: Context) {
- val res = context.resources
- iconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size_sp)
- iconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
- aodIconAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation)
- }
-
- private fun shouldShowNotificationIcon(
- entry: NotificationEntry,
- showAmbient: Boolean,
- showLowPriority: Boolean,
- hideDismissed: Boolean,
- hideRepliedMessages: Boolean,
- hideCurrentMedia: Boolean,
- hidePulsing: Boolean
- ): Boolean {
- if (!showAmbient && sectionStyleProvider.isMinimized(entry)) {
- return false
- }
- if (hideCurrentMedia && entry.key == mediaManager.mediaNotificationKey) {
- return false
- }
- if (!showLowPriority && sectionStyleProvider.isSilent(entry)) {
- return false
- }
- if (entry.isRowDismissed && hideDismissed) {
- return false
- }
- if (hideRepliedMessages && entry.isLastMessageFromReply) {
- return false
- }
- // showAmbient == show in shade but not shelf
- if (!showAmbient && entry.shouldSuppressStatusBar()) {
- return false
- }
- if (
- hidePulsing &&
- entry.showingPulsing() &&
- (!wakeUpCoordinator.notificationsFullyHidden || !entry.isPulseSuppressed)
- ) {
- return false
- }
- return if (bubblesOptional.isPresent && bubblesOptional.get().isBubbleExpanded(entry.key)) {
- false
- } else true
- }
-
- private fun updateNotificationIcons() {
- Trace.beginSection("NotificationIconAreaController.updateNotificationIcons")
- updateStatusBarIcons()
- updateShelfIcons()
- updateAodNotificationIcons()
- applyNotificationIconsTint()
- Trace.endSection()
- }
-
- private fun updateShelfIcons() {
- if (shelfIcons == null) {
- return
- }
- updateIconsForLayout(
- { entry: NotificationEntry -> entry.icons.shelfIcon },
- shelfIcons,
- showAmbient = true,
- showLowPriority = true,
- hideDismissed = false,
- hideRepliedMessages = false,
- hideCurrentMedia = false,
- hidePulsing = false
- )
- }
-
- /**
- * Updates the notification icons for a host layout. This will ensure that the notification host
- * layout will have the same icons like the ones in here.
- *
- * @param function A function to look up an icon view based on an entry
- * @param hostLayout which layout should be updated
- * @param showAmbient should ambient notification icons be shown
- * @param showLowPriority should icons from silent notifications be shown
- * @param hideDismissed should dismissed icons be hidden
- * @param hideRepliedMessages should messages that have been replied to be hidden
- * @param hidePulsing should pulsing notifications be hidden
- */
- private fun updateIconsForLayout(
- function: Function<NotificationEntry, StatusBarIconView?>,
- hostLayout: NotificationIconContainer?,
- showAmbient: Boolean,
- showLowPriority: Boolean,
- hideDismissed: Boolean,
- hideRepliedMessages: Boolean,
- hideCurrentMedia: Boolean,
- hidePulsing: Boolean,
- ) {
- val toShow = ArrayList<StatusBarIconView>(notificationEntries.size)
- // Filter out ambient notifications and notification children.
- for (i in notificationEntries.indices) {
- val entry = notificationEntries[i].representativeEntry
- if (entry != null && entry.row != null) {
- if (
- shouldShowNotificationIcon(
- entry,
- showAmbient,
- showLowPriority,
- hideDismissed,
- hideRepliedMessages,
- hideCurrentMedia,
- hidePulsing
- )
- ) {
- val iconView = function.apply(entry)
- if (iconView != null) {
- toShow.add(iconView)
- }
- }
- }
- }
-
- // In case we are changing the suppression of a group, the replacement shouldn't flicker
- // and it should just be replaced instead. We therefore look for notifications that were
- // just replaced by the child or vice-versa to suppress this.
- val replacingIcons = ArrayMap<String, ArrayList<StatusBarIcon>>()
- val toRemove = ArrayList<View>()
- for (i in 0 until hostLayout!!.childCount) {
- val child = hostLayout.getChildAt(i) as? StatusBarIconView ?: continue
- if (!toShow.contains(child)) {
- var iconWasReplaced = false
- val removedGroupKey = child.notification.groupKey
- for (j in toShow.indices) {
- val candidate = toShow[j]
- if (
- candidate.sourceIcon.sameAs(child.sourceIcon) &&
- candidate.notification.groupKey == removedGroupKey
- ) {
- if (!iconWasReplaced) {
- iconWasReplaced = true
- } else {
- iconWasReplaced = false
- break
- }
- }
- }
- if (iconWasReplaced) {
- var statusBarIcons = replacingIcons[removedGroupKey]
- if (statusBarIcons == null) {
- statusBarIcons = ArrayList()
- replacingIcons[removedGroupKey] = statusBarIcons
- }
- statusBarIcons.add(child.statusBarIcon)
- }
- toRemove.add(child)
- }
- }
- // removing all duplicates
- val duplicates = ArrayList<String?>()
- for (key in replacingIcons.keys) {
- val statusBarIcons = replacingIcons[key]!!
- if (statusBarIcons.size != 1) {
- duplicates.add(key)
- }
- }
- replacingIcons.removeAll(duplicates)
- hostLayout.setReplacingIcons(replacingIcons)
- val toRemoveCount = toRemove.size
- for (i in 0 until toRemoveCount) {
- hostLayout.removeView(toRemove[i])
- }
- val params = generateIconLayoutParams()
- for (i in toShow.indices) {
- val v = toShow[i]
- // The view might still be transiently added if it was just removed and added again
- hostLayout.removeTransientView(v)
- if (v.parent == null) {
- if (hideDismissed) {
- v.setOnDismissListener(updateStatusBarIcons)
- }
- hostLayout.addView(v, i, params)
- }
- }
- hostLayout.setChangingViewPositions(true)
- // Re-sort notification icons
- val childCount = hostLayout.childCount
- for (i in 0 until childCount) {
- val actual = hostLayout.getChildAt(i)
- val expected = toShow[i]
- if (actual === expected) {
- continue
- }
- hostLayout.removeView(expected)
- hostLayout.addView(expected, i)
- }
- hostLayout.setChangingViewPositions(false)
- hostLayout.setReplacingIcons(null)
- }
-
- /** Applies [.mIconTint] to the notification icons. */
- private fun applyNotificationIconsTint() {
- for (i in 0 until notificationIcons!!.childCount) {
- val iv = notificationIcons!!.getChildAt(i) as StatusBarIconView
- if (iv.width != 0) {
- updateTintForIcon(iv, iconTint)
- } else {
- iv.executeOnLayout { updateTintForIcon(iv, iconTint) }
- }
- }
- updateAodIconColors()
- }
-
- private fun updateTintForIcon(v: StatusBarIconView, tint: Int) {
- val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
- var color = StatusBarIconView.NO_COLOR
- val colorize = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
- if (colorize) {
- color = DarkIconDispatcher.getTint(tintAreas, v, tint)
- }
- v.staticDrawableColor = color
- v.setDecorColor(tint)
- }
-
- private fun reloadAodColor() {
- aodIconTint =
- Utils.getColorAttrDefaultColor(
- context,
- R.attr.wallpaperTextColor,
- DEFAULT_AOD_ICON_COLOR
- )
- }
-
- private fun updateAodIconColors() {
- if (aodIcons != null) {
- for (i in 0 until aodIcons!!.childCount) {
- val iv = aodIcons!!.getChildAt(i) as StatusBarIconView
- if (iv.width != 0) {
- updateTintForIcon(iv, aodIconTint)
- } else {
- iv.executeOnLayout { updateTintForIcon(iv, aodIconTint) }
- }
- }
- }
- }
-
companion object {
- @ColorInt private val DEFAULT_AOD_ICON_COLOR = -0x1
-
val unsupported: Nothing
get() =
error(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 0d2f00aa3627..75926194af58 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -15,103 +15,306 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-import android.content.res.Resources
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.graphics.Rect
import android.view.View
-import androidx.annotation.DimenRes
+import android.view.ViewPropertyAnimator
+import android.widget.FrameLayout
+import androidx.collection.ArrayMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.internal.policy.SystemBarUtils
+import com.android.internal.util.ContrastColorUtil
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.CrossFadeHelper
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.NotificationUtils
+import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder.IconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onConfigChanged
+import com.android.systemui.util.children
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
+import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.kotlin.stateFlow
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
+import javax.inject.Inject
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** Binds a [NotificationIconContainer] to its [view model][NotificationIconContainerViewModel]. */
object NotificationIconContainerViewBinder {
+ @JvmStatic
fun bind(
view: NotificationIconContainer,
viewModel: NotificationIconContainerViewModel,
+ configuration: ConfigurationState,
configurationController: ConfigurationController,
dozeParameters: DozeParameters,
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
+ viewStore: IconViewStore,
): DisposableHandle {
+ val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
return view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch { viewModel.animationsEnabled.collect(view::setAnimationsEnabled) }
- launch {
- viewModel.isDozing.collect { (isDozing, animate) ->
- val animateIfNotBlanking = animate && !dozeParameters.displayNeedsBlanking
- view.setDozing(isDozing, animateIfNotBlanking, /* delay= */ 0) {
- viewModel.completeDozeAnimation()
- }
- }
- }
- // TODO(278765923): this should live where AOD is bound, not inside of the NIC
+ launch { bindAnimationsEnabled(viewModel, view) }
+ launch { bindIsDozing(viewModel, view, dozeParameters) }
+ // TODO(b/278765923): this should live where AOD is bound, not inside of the NIC
// view-binder
launch {
- val iconAppearTranslation =
- view.resources.getConfigAwareDimensionPixelSize(
- this,
- configurationController,
- R.dimen.shelf_appear_translation,
- )
bindVisibility(
viewModel,
view,
+ configuration,
featureFlags,
screenOffAnimationController,
- iconAppearTranslation,
- ) {
- viewModel.completeVisibilityAnimation()
+ )
+ }
+ launch { bindIconColors(viewModel, view, contrastColorUtil) }
+ launch {
+ bindIconViewData(
+ viewModel,
+ view,
+ configuration,
+ configurationController,
+ viewStore,
+ )
+ }
+ launch { bindIsolatedIcon(viewModel, view, viewStore) }
+ }
+ }
+ }
+
+ private suspend fun bindAnimationsEnabled(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer
+ ) {
+ viewModel.animationsEnabled.collect(view::setAnimationsEnabled)
+ }
+
+ private suspend fun bindIconColors(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ viewModel.iconColors
+ .mapNotNull { lookup -> lookup.iconColors(view.viewBounds) }
+ .collect { iconLookup -> applyTint(view, iconLookup, contrastColorUtil) }
+ }
+
+ private suspend fun bindIsDozing(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ dozeParameters: DozeParameters,
+ ) {
+ viewModel.isDozing.collect { isDozing ->
+ if (isDozing.isAnimating) {
+ val animate = !dozeParameters.displayNeedsBlanking
+ view.setDozing(
+ /* dozing = */ isDozing.value,
+ /* fade = */ animate,
+ /* delay = */ 0,
+ /* endRunnable = */ isDozing::stopAnimating,
+ )
+ } else {
+ view.setDozing(
+ /* dozing = */ isDozing.value,
+ /* fade= */ false,
+ /* delay= */ 0,
+ )
+ }
+ }
+ }
+
+ private suspend fun bindIsolatedIcon(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ viewStore: IconViewStore,
+ ) {
+ coroutineScope {
+ launch {
+ viewModel.isolatedIconLocation.collect { location ->
+ view.setIsolatedIconLocation(location, true)
+ }
+ }
+ launch {
+ viewModel.isolatedIcon.collect { iconInfo ->
+ val iconView = iconInfo.value?.let { viewStore.iconView(it.notifKey) }
+ if (iconInfo.isAnimating) {
+ view.showIconIsolatedAnimated(iconView, iconInfo::stopAnimating)
+ } else {
+ view.showIconIsolated(iconView)
}
}
}
}
}
+
+ private suspend fun bindIconViewData(
+ viewModel: NotificationIconContainerViewModel,
+ view: NotificationIconContainer,
+ configuration: ConfigurationState,
+ configurationController: ConfigurationController,
+ viewStore: IconViewStore,
+ ): Unit = coroutineScope {
+ val iconSizeFlow: Flow<Int> =
+ configuration.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_icon_size_sp,
+ )
+ val iconHorizontalPaddingFlow: Flow<Int> =
+ configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin)
+ val statusBarHeightFlow: StateFlow<Int> =
+ stateFlow(changedSignals = configurationController.onConfigChanged) {
+ SystemBarUtils.getStatusBarHeight(view.context)
+ }
+ val layoutParams: Flow<FrameLayout.LayoutParams> =
+ combine(iconSizeFlow, iconHorizontalPaddingFlow, statusBarHeightFlow) {
+ iconSize,
+ iconHPadding,
+ statusBarHeight,
+ ->
+ FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
+ }
+
+ launch {
+ layoutParams.collect { params: FrameLayout.LayoutParams ->
+ for (child in view.children) {
+ child.layoutParams = params
+ }
+ }
+ }
+
+ var prevIcons = IconsViewData()
+ viewModel.iconsViewData.sample(layoutParams, ::Pair).collect {
+ (iconsData: IconsViewData, layoutParams: FrameLayout.LayoutParams),
+ ->
+ val iconsDiff = IconsViewData.computeDifference(iconsData, prevIcons)
+ prevIcons = iconsData
+
+ val replacingIcons =
+ iconsDiff.groupReplacements.mapValuesNotNullTo(ArrayMap()) { (_, v) ->
+ viewStore.iconView(v.notifKey)?.statusBarIcon
+ }
+ view.setReplacingIcons(replacingIcons)
+
+ val childrenByNotifKey: Map<String, StatusBarIconView> =
+ view.children.filterIsInstance<StatusBarIconView>().associateByTo(ArrayMap()) {
+ it.notification.key
+ }
+
+ iconsDiff.removed
+ .mapNotNull { key -> childrenByNotifKey[key] }
+ .forEach { child -> view.removeView(child) }
+
+ val toAdd = iconsDiff.added.mapNotNull { viewStore.iconView(it.notifKey) }
+ for ((i, sbiv) in toAdd.withIndex()) {
+ // The view might still be transiently added if it was just removed
+ // and added again
+ view.removeTransientView(sbiv)
+ view.addView(sbiv, i, layoutParams)
+ }
+
+ view.setChangingViewPositions(true)
+ // Re-sort notification icons
+ val childCount = view.childCount
+ for (i in 0 until childCount) {
+ val actual = view.getChildAt(i)
+ val expected = viewStore.iconView(iconsData.visibleKeys[i].notifKey)!!
+ if (actual === expected) {
+ continue
+ }
+ view.removeView(expected)
+ view.addView(expected, i)
+ }
+ view.setChangingViewPositions(false)
+
+ view.setReplacingIcons(null)
+ }
+ }
+
+ // TODO(b/305739416): Once StatusBarIconView has its own Recommended Architecture stack, this
+ // can be moved there and cleaned up.
+ private fun applyTint(
+ view: NotificationIconContainer,
+ iconColors: IconColors,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ view.children
+ .filterIsInstance<StatusBarIconView>()
+ .filter { it.width != 0 }
+ .forEach { iv -> updateTintForIcon(iv, iconColors, contrastColorUtil) }
+ }
+
+ private fun updateTintForIcon(
+ v: StatusBarIconView,
+ iconColors: IconColors,
+ contrastColorUtil: ContrastColorUtil,
+ ) {
+ val isPreL = java.lang.Boolean.TRUE == v.getTag(R.id.icon_is_pre_L)
+ val isColorized = !isPreL || NotificationUtils.isGrayscale(v, contrastColorUtil)
+ v.staticDrawableColor = iconColors.staticDrawableColor(v.viewBounds, isColorized)
+ v.setDecorColor(iconColors.tint)
+ }
+
private suspend fun bindVisibility(
viewModel: NotificationIconContainerViewModel,
view: NotificationIconContainer,
+ configuration: ConfigurationState,
featureFlags: FeatureFlagsClassic,
screenOffAnimationController: ScreenOffAnimationController,
- iconAppearTranslation: StateFlow<Int>,
- onAnimationEnd: () -> Unit,
- ) {
+ ): Unit = coroutineScope {
+ val iconAppearTranslation =
+ configuration.getDimensionPixelSize(R.dimen.shelf_appear_translation).stateIn(this)
val statusViewMigrated = featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)
- viewModel.isVisible.collect { (isVisible, animate) ->
+ viewModel.isVisible.collect { isVisible ->
view.animate().cancel()
+ val animatorListener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ isVisible.stopAnimating()
+ }
+ }
when {
- !animate -> {
+ !isVisible.isAnimating -> {
view.alpha = 1f
if (!statusViewMigrated) {
view.translationY = 0f
}
- view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
+ view.visibility = if (isVisible.value) View.VISIBLE else View.INVISIBLE
}
featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> {
animateInIconTranslation(view, statusViewMigrated)
- if (isVisible) {
- CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ if (isVisible.value) {
+ CrossFadeHelper.fadeIn(view, animatorListener)
} else {
- CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ CrossFadeHelper.fadeOut(view, animatorListener)
}
}
- !isVisible -> {
+ !isVisible.value -> {
// Let's make sure the icon are translated to 0, since we cancelled it above
animateInIconTranslation(view, statusViewMigrated)
- CrossFadeHelper.fadeOut(view, onAnimationEnd)
+ CrossFadeHelper.fadeOut(view, animatorListener)
}
view.visibility != View.VISIBLE -> {
// No fading here, let's just appear the icons instead!
@@ -122,14 +325,14 @@ object NotificationIconContainerViewBinder {
animate = screenOffAnimationController.shouldAnimateAodIcons(),
iconAppearTranslation.value,
statusViewMigrated,
+ animatorListener,
)
- onAnimationEnd()
}
else -> {
// Let's make sure the icons are translated to 0, since we cancelled it above
animateInIconTranslation(view, statusViewMigrated)
// We were fading out, let's fade in instead
- CrossFadeHelper.fadeIn(view, onAnimationEnd)
+ CrossFadeHelper.fadeIn(view, animatorListener)
}
}
}
@@ -140,18 +343,20 @@ object NotificationIconContainerViewBinder {
animate: Boolean,
iconAppearTranslation: Int,
statusViewMigrated: Boolean,
+ animatorListener: Animator.AnimatorListener,
) {
if (animate) {
if (!statusViewMigrated) {
view.translationY = -iconAppearTranslation.toFloat()
}
view.alpha = 0f
- animateInIconTranslation(view, statusViewMigrated)
view
.animate()
.alpha(1f)
.setInterpolator(Interpolators.LINEAR)
.setDuration(AOD_ICONS_APPEAR_DURATION)
+ .apply { if (statusViewMigrated) animateInIconTranslation() }
+ .setListener(animatorListener)
.start()
} else {
view.alpha = 1.0f
@@ -163,24 +368,59 @@ object NotificationIconContainerViewBinder {
private fun animateInIconTranslation(view: View, statusViewMigrated: Boolean) {
if (!statusViewMigrated) {
- view
- .animate()
- .setInterpolator(Interpolators.DECELERATE_QUINT)
- .translationY(0f)
- .setDuration(AOD_ICONS_APPEAR_DURATION)
- .start()
+ view.animate().animateInIconTranslation().setDuration(AOD_ICONS_APPEAR_DURATION).start()
}
}
+ private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator =
+ setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f)
+
private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+
+ private val View.viewBounds: Rect
+ get() {
+ val tmpArray = intArrayOf(0, 0)
+ getLocationOnScreen(tmpArray)
+ return Rect(
+ /* left = */ tmpArray[0],
+ /* top = */ tmpArray[1],
+ /* right = */ left + width,
+ /* bottom = */ top + height,
+ )
+ }
+
+ /** External storage for [StatusBarIconView] instances. */
+ fun interface IconViewStore {
+ fun iconView(key: String): StatusBarIconView?
+ }
}
-fun Resources.getConfigAwareDimensionPixelSize(
- scope: CoroutineScope,
- configurationController: ConfigurationController,
- @DimenRes id: Int,
-): StateFlow<Int> =
- scope.stateFlow(
- changedSignals = configurationController.onDensityOrFontScaleChanged,
- getValue = { getDimensionPixelSize(id) }
- )
+/** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
+class ShelfNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.shelfIcon
+}
+
+/** [IconViewStore] for the always-on display. */
+class AlwaysOnDisplayNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.aodIcon
+}
+
+/** [IconViewStore] for the status bar. */
+class StatusBarNotificationIconViewStore
+@Inject
+constructor(
+ private val notifCollection: NotifCollection,
+) : IconViewStore {
+ override fun iconView(key: String): StatusBarIconView? =
+ notifCollection.getEntry(key)?.icons?.statusBarIcon
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
index 3289a3ce5574..120d342b18d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModel.kt
@@ -15,6 +15,10 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Color
+import android.graphics.Rect
+import androidx.annotation.ColorInt
+import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.flags.FeatureFlagsClassic
@@ -23,8 +27,14 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.AlwaysOnDisplayNotificationIconsInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.util.kotlin.pairwise
@@ -32,11 +42,13 @@ import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
+import com.android.systemui.util.ui.zip
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/** View-model for the row of notification icons displayed on the always-on display. */
@@ -44,9 +56,11 @@ import kotlinx.coroutines.flow.map
class NotificationIconContainerAlwaysOnDisplayViewModel
@Inject
constructor(
+ configuration: ConfigurationState,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val featureFlags: FeatureFlagsClassic,
+ iconsInteractor: AlwaysOnDisplayNotificationIconsInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
@@ -54,8 +68,10 @@ constructor(
shadeInteractor: ShadeInteractor,
) : NotificationIconContainerViewModel {
- private val onDozeAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
- private val onVisAnimationComplete = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+ override val iconColors: Flow<ColorLookup> =
+ configuration.getColorAttr(R.attr.wallpaperTextColor, DEFAULT_AOD_ICON_COLOR).map { tint ->
+ ColorLookup { IconColorsImpl(tint) }
+ }
override val animationsEnabled: Flow<Boolean> =
combine(
@@ -80,7 +96,7 @@ constructor(
AnimatableEvent(isDozing, animate)
}
.distinctUntilChanged()
- .toAnimatedValueFlow(completionEvents = onDozeAnimationComplete)
+ .toAnimatedValueFlow()
override val isVisible: Flow<AnimatedValue<Boolean>> =
combine(
@@ -90,46 +106,54 @@ constructor(
isPulseExpandingAnimated(),
) {
onKeyguard: Boolean,
- bypassEnabled: Boolean,
- (notifsFullyHidden: Boolean, isAnimatingHide: Boolean),
- (pulseExpanding: Boolean, isAnimatingPulse: Boolean),
+ isBypassEnabled: Boolean,
+ notifsFullyHidden: AnimatedValue<Boolean>,
+ pulseExpanding: AnimatedValue<Boolean>,
->
- val isAnimating = isAnimatingHide || isAnimatingPulse
when {
// Hide the AOD icons if we're not in the KEYGUARD state unless the screen off
// animation is playing, in which case we want them to be visible if we're
// animating in the AOD UI and will be switching to KEYGUARD shortly.
!onKeyguard && !screenOffAnimationController.shouldShowAodIconsWhenShade() ->
- AnimatedValue(false, isAnimating = false)
- // If we're bypassing, then we're visible
- bypassEnabled -> AnimatedValue(true, isAnimating)
- // If we are pulsing (and not bypassing), then we are hidden
- pulseExpanding -> AnimatedValue(false, isAnimating)
- // If notifs are fully gone, then we're visible
- notifsFullyHidden -> AnimatedValue(true, isAnimating)
- // Otherwise, we're hidden
- else -> AnimatedValue(false, isAnimating)
+ AnimatedValue.NotAnimating(false)
+ else ->
+ zip(notifsFullyHidden, pulseExpanding) {
+ areNotifsFullyHidden,
+ isPulseExpanding,
+ ->
+ when {
+ // If we're bypassing, then we're visible
+ isBypassEnabled -> true
+ // If we are pulsing (and not bypassing), then we are hidden
+ isPulseExpanding -> false
+ // If notifs are fully gone, then we're visible
+ areNotifsFullyHidden -> true
+ // Otherwise, we're hidden
+ else -> false
+ }
+ }
}
}
.distinctUntilChanged()
- override fun completeDozeAnimation() {
- onDozeAnimationComplete.tryEmit(Unit)
- }
+ override val iconsViewData: Flow<IconsViewData> =
+ iconsInteractor.aodNotifs.map { entries ->
+ IconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.aodIcon) },
+ )
+ }
- override fun completeVisibilityAnimation() {
- onVisAnimationComplete.tryEmit(Unit)
- }
+ override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
+ flowOf(AnimatedValue.NotAnimating(null))
+ override val isolatedIconLocation: Flow<Rect> = emptyFlow()
/** Is there an expanded pulse, are we animating in response? */
private fun isPulseExpandingAnimated(): Flow<AnimatedValue<Boolean>> {
return notificationsKeyguardInteractor.isPulseExpanding
.pairwise(initialValue = null)
// If pulsing changes, start animating, unless it's the first emission
- .map { (prev, expanding) ->
- AnimatableEvent(expanding!!, startAnimating = prev != null)
- }
- .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ .map { (prev, expanding) -> AnimatableEvent(expanding, startAnimating = prev != null) }
+ .toAnimatedValueFlow()
}
/** Are notifications completely hidden from view, are we animating in response? */
@@ -151,10 +175,18 @@ constructor(
// We only want the appear animations to happen when the notifications
// get fully hidden, since otherwise the un-hide animation overlaps.
featureFlags.isEnabled(Flags.NEW_AOD_TRANSITION) -> true
- else -> fullyHidden!!
+ else -> fullyHidden
}
- AnimatableEvent(fullyHidden!!, animate)
+ AnimatableEvent(fullyHidden, animate)
}
- .toAnimatedValueFlow(completionEvents = onVisAnimationComplete)
+ .toAnimatedValueFlow()
+ }
+
+ private class IconColorsImpl(override val tint: Int) : IconColors {
+ override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int = tint
+ }
+
+ companion object {
+ @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
index c44a2b60142c..c6aabb7527da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerShelfViewModel.kt
@@ -15,18 +15,37 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
+import com.android.systemui.statusbar.notification.icon.domain.interactor.NotificationIconsInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
import com.android.systemui.util.ui.AnimatedValue
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/** View-model for the overflow row of notification icons displayed in the notification shade. */
-class NotificationIconContainerShelfViewModel @Inject constructor() :
- NotificationIconContainerViewModel {
+class NotificationIconContainerShelfViewModel
+@Inject
+constructor(
+ interactor: NotificationIconsInteractor,
+) : NotificationIconContainerViewModel {
+
override val animationsEnabled: Flow<Boolean> = flowOf(true)
override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override fun completeDozeAnimation() {}
- override fun completeVisibilityAnimation() {}
+ override val iconColors: Flow<ColorLookup> = emptyFlow()
+ override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
+ flowOf(AnimatedValue.NotAnimating(null))
+ override val isolatedIconLocation: Flow<Rect> = emptyFlow()
+
+ override val iconsViewData: Flow<IconsViewData> =
+ interactor.filteredNotifSet().map { entries ->
+ IconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.shelfIcon) },
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 035687a4a91b..4d14024fcd99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -15,19 +15,39 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
+import com.android.systemui.statusbar.notification.icon.domain.interactor.StatusBarNotificationIconsInteractor
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.ColorLookup
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconColors
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconsViewData
+import com.android.systemui.statusbar.phone.domain.interactor.DarkIconInteractor
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.toAnimatedValueFlow
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
/** View-model for the row of notification icons displayed in the status bar, */
class NotificationIconContainerStatusBarViewModel
@Inject
constructor(
+ darkIconInteractor: DarkIconInteractor,
+ iconsInteractor: StatusBarNotificationIconsInteractor,
+ headsUpIconInteractor: HeadsUpNotificationIconInteractor,
keyguardInteractor: KeyguardInteractor,
+ notificationsInteractor: ActiveNotificationsInteractor,
shadeInteractor: ShadeInteractor,
) : NotificationIconContainerViewModel {
override val animationsEnabled: Flow<Boolean> =
@@ -38,8 +58,66 @@ constructor(
panelTouchesEnabled && !isKeyguardShowing
}
+ override val iconColors: Flow<ColorLookup> =
+ combine(
+ darkIconInteractor.tintAreas,
+ darkIconInteractor.tintColor,
+ // Included so that tints are re-applied after entries are changed.
+ notificationsInteractor.notifications,
+ ) { areas, tint, _ ->
+ ColorLookup { viewBounds: Rect ->
+ if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+ IconColorsImpl(tint, areas)
+ } else {
+ null
+ }
+ }
+ }
+
override val isDozing: Flow<AnimatedValue<Boolean>> = emptyFlow()
override val isVisible: Flow<AnimatedValue<Boolean>> = emptyFlow()
- override fun completeDozeAnimation() {}
- override fun completeVisibilityAnimation() {}
+
+ override val iconsViewData: Flow<IconsViewData> =
+ iconsInteractor.statusBarNotifs.map { entries ->
+ IconsViewData(
+ visibleKeys = entries.mapNotNull { it.toIconInfo(it.statusBarIcon) },
+ )
+ }
+
+ override val isolatedIcon: Flow<AnimatedValue<IconInfo?>> =
+ headsUpIconInteractor.isolatedNotification
+ .pairwise(initialValue = null)
+ .sample(combine(iconsViewData, shadeInteractor.shadeExpansion, ::Pair)) {
+ (prev, isolatedNotif),
+ (iconsViewData, shadeExpansion),
+ ->
+ val iconInfo =
+ isolatedNotif?.let {
+ iconsViewData.visibleKeys.firstOrNull { it.notifKey == isolatedNotif }
+ }
+ val animate =
+ when {
+ isolatedNotif == prev -> false
+ isolatedNotif == null || prev == null -> shadeExpansion == 0f
+ else -> false
+ }
+ AnimatableEvent(iconInfo, animate)
+ }
+ .toAnimatedValueFlow()
+
+ override val isolatedIconLocation: Flow<Rect> =
+ headsUpIconInteractor.isolatedIconLocation.filterNotNull()
+
+ private class IconColorsImpl(
+ override val tint: Int,
+ private val areas: Collection<Rect>,
+ ) : IconColors {
+ override fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int {
+ return if (isColorized && DarkIconDispatcher.isInAreas(areas, viewBounds)) {
+ tint
+ } else {
+ DarkIconDispatcher.DEFAULT_ICON_TINT
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
index 65eb22075ec7..a611323201e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerViewModel.kt
@@ -15,6 +15,12 @@
*/
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
+import android.graphics.drawable.Icon
+import androidx.collection.ArrayMap
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel.IconInfo
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import com.android.systemui.util.kotlin.mapValuesNotNullTo
import com.android.systemui.util.ui.AnimatedValue
import kotlinx.coroutines.flow.Flow
@@ -23,6 +29,7 @@ import kotlinx.coroutines.flow.Flow
* AOD.
*/
interface NotificationIconContainerViewModel {
+
/** Are changes to the icon container animated? */
val animationsEnabled: Flow<Boolean>
@@ -32,15 +39,158 @@ interface NotificationIconContainerViewModel {
/** Is the icon container visible? */
val isVisible: Flow<AnimatedValue<Boolean>>
- /**
- * Signal completion of the [isDozing] animation; if [isDozing]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
- */
- fun completeDozeAnimation()
+ /** The colors with which to display the notification icons. */
+ val iconColors: Flow<ColorLookup>
+
+ /** [IconsViewData] indicating which icons to display in the view. */
+ val iconsViewData: Flow<IconsViewData>
+
+ /** An Icon to show "isolated" in the IconContainer. */
+ val isolatedIcon: Flow<AnimatedValue<IconInfo?>>
+
+ /** Location to show an isolated icon, if there is one. */
+ val isolatedIconLocation: Flow<Rect>
/**
- * Signal completion of the [isVisible] animation; if [isVisible]'s [AnimatedValue.isAnimating]
- * property was `true`, calling this method will update it to `false.
+ * Lookup the colors to use for the notification icons based on the bounds of the icon
+ * container. A result of `null` indicates that no color changes should be applied.
*/
- fun completeVisibilityAnimation()
+ fun interface ColorLookup {
+ fun iconColors(viewBounds: Rect): IconColors?
+ }
+
+ /** Colors to apply to notification icons. */
+ interface IconColors {
+
+ /** A tint to apply to the icons. */
+ val tint: Int
+
+ /**
+ * Returns the color to be applied to an icon, based on that icon's view bounds and whether
+ * or not the notification icon is colorized.
+ */
+ fun staticDrawableColor(viewBounds: Rect, isColorized: Boolean): Int
+ }
+
+ /** Encapsulates the collection of notification icons present on the device. */
+ data class IconsViewData(
+ /** Icons that are visible in the container. */
+ val visibleKeys: List<IconInfo> = emptyList(),
+ /** Keys of icons that are "behind" the overflow dot. */
+ val collapsedKeys: Set<String> = emptySet(),
+ /** Whether the overflow dot should be shown regardless if [collapsedKeys] is empty. */
+ val forceShowDot: Boolean = false,
+ ) {
+ /** The difference between two [IconsViewData]s. */
+ data class Diff(
+ /** Icons added in the newer dataset. */
+ val added: List<IconInfo> = emptyList(),
+ /** Icons removed from the older dataset. */
+ val removed: List<String> = emptyList(),
+ /**
+ * Groups whose icon was replaced with a single new notification icon. The key of the
+ * [Map] is the notification group key, and the value is the new icon.
+ *
+ * Specifically, this models a difference where the older dataset had notification
+ * groups with a single icon in the set, and the newer dataset has a single, different
+ * icon for the same group. A view binder can use this information for special
+ * animations for this specific change.
+ */
+ val groupReplacements: Map<String, IconInfo> = emptyMap(),
+ )
+
+ companion object {
+ /**
+ * Returns an [IconsViewData.Diff] calculated from a [new] and [previous][prev]
+ * [IconsViewData] state.
+ */
+ fun computeDifference(new: IconsViewData, prev: IconsViewData): Diff {
+ val added: List<IconInfo> =
+ new.visibleKeys.filter {
+ it.notifKey !in prev.visibleKeys.asSequence().map { it.notifKey }
+ }
+ val removed: List<IconInfo> =
+ prev.visibleKeys.filter {
+ it.notifKey !in new.visibleKeys.asSequence().map { it.notifKey }
+ }
+ val groupsToShow: Set<IconGroupInfo> =
+ new.visibleKeys.asSequence().map { it.groupInfo }.toSet()
+ val replacements: ArrayMap<String, IconInfo> =
+ removed
+ .asSequence()
+ .filter { keyToRemove -> keyToRemove.groupInfo in groupsToShow }
+ .groupBy { it.groupInfo.groupKey }
+ .mapValuesNotNullTo(ArrayMap()) { (_, vs) ->
+ vs.takeIf { it.size == 1 }?.get(0)
+ }
+ return Diff(added, removed.map { it.notifKey }, replacements)
+ }
+ }
+ }
+
+ /** An Icon, and keys for unique identification. */
+ data class IconInfo(
+ val sourceIcon: Icon,
+ val notifKey: String,
+ val groupKey: String,
+ )
+}
+
+/**
+ * Construct an [IconInfo] out of an [ActiveNotificationModel], or return `null` if one cannot be
+ * created due to missing information.
+ */
+fun ActiveNotificationModel.toIconInfo(sourceIcon: Icon?): IconInfo? {
+ return sourceIcon?.let {
+ groupKey?.let { groupKey ->
+ IconInfo(
+ sourceIcon = sourceIcon,
+ notifKey = key,
+ groupKey = groupKey,
+ )
+ }
+ }
+}
+
+private val IconInfo.groupInfo: IconGroupInfo
+ get() = IconGroupInfo(sourceIcon, groupKey)
+
+private data class IconGroupInfo(
+ val sourceIcon: Icon,
+ val groupKey: String,
+) {
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as IconGroupInfo
+
+ if (groupKey != other.groupKey) return false
+ return sourceIcon.sameAs(other.sourceIcon)
+ }
+
+ override fun hashCode(): Int {
+ var result = groupKey.hashCode()
+ result = 31 * result + sourceIcon.type.hashCode()
+ when (sourceIcon.type) {
+ Icon.TYPE_BITMAP,
+ Icon.TYPE_ADAPTIVE_BITMAP -> {
+ result = 31 * result + sourceIcon.bitmap.hashCode()
+ }
+ Icon.TYPE_DATA -> {
+ result = 31 * result + sourceIcon.dataLength.hashCode()
+ result = 31 * result + sourceIcon.dataOffset.hashCode()
+ }
+ Icon.TYPE_RESOURCE -> {
+ result = 31 * result + sourceIcon.resId.hashCode()
+ result = 31 * result + sourceIcon.resPackage.hashCode()
+ }
+ Icon.TYPE_URI,
+ Icon.TYPE_URI_ADAPTIVE_BITMAP -> {
+ result = 31 * result + sourceIcon.uriString.hashCode()
+ }
+ }
+ return result
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
new file mode 100644
index 000000000000..78370baa4311
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import android.graphics.drawable.Icon
+
+/** Model for entries in the notification stack. */
+data class ActiveNotificationModel(
+ /** Notification key associated with this entry. */
+ val key: String,
+ /** Notification group key associated with this entry. */
+ val groupKey: String?,
+ /** Is this entry in the ambient / minimized section (lowest priority)? */
+ val isAmbient: Boolean,
+ /**
+ * Is this entry dismissed? This is `true` when the user has dismissed the notification in the
+ * UI, but `NotificationManager` has not yet signalled to us that it has received the dismissal.
+ */
+ val isRowDismissed: Boolean,
+ /** Is this entry in the silent section? */
+ val isSilent: Boolean,
+ /**
+ * Does this entry represent a conversation, the last message of which was from a remote input
+ * reply?
+ */
+ val isLastMessageFromReply: Boolean,
+ /** Is this entry suppressed from appearing in the status bar as an icon? */
+ val isSuppressedFromStatusBar: Boolean,
+ /** Is this entry actively pulsing on AOD or bypassed-keyguard? */
+ val isPulsing: Boolean,
+ /** Icon to display on AOD. */
+ val aodIcon: Icon?,
+ /** Icon to display in the notification shelf. */
+ val shelfIcon: Icon?,
+ /** Icon to display in the status bar. */
+ val statusBarIcon: Icon?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index e4d96c30f15c..6bb957339b6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -67,18 +67,6 @@ public interface NotificationListContainer extends
void notifyGroupChildRemoved(ExpandableView row, ViewGroup childrenContainer);
/**
- * Generate an animation for an added child view.
- * @param child The view to be added.
- * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
- */
- void generateAddAnimation(ExpandableView child, boolean fromMoreCard);
-
- /**
- * Generate a child order changed event.
- */
- void generateChildOrderChangedEvent();
-
- /**
* Returns the number of children in the NotificationListContainer.
*
* @return the number of children in the NotificationListContainer
@@ -187,21 +175,6 @@ public interface NotificationListContainer extends
default void bindRow(ExpandableNotificationRow row) {}
/**
- * Does this list contain a given view. True by default is fine, since we only ask this if the
- * view has a parent.
- */
- default boolean containsView(View v) {
- return true;
- }
-
- /**
- * Tells the container that an animation is about to expand it.
- */
- default void setWillExpand(boolean willExpand) {}
-
- void setNotificationActivityStarter(NotificationActivityStarter notificationActivityStarter);
-
- /**
* @return the start location where we start clipping notifications.
*/
default int getTopClippingStartLocation() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index dba93d9718cb..1e9cfa8d1d3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -107,6 +107,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -324,7 +325,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private NotificationsController mNotificationsController;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
- private boolean mGenerateChildOrderChangedEvent;
private final HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
private final HashSet<ExpandableView> mClearTransientViewsWhenFinished = new HashSet<>();
private final HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
@@ -695,7 +695,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
super.onFinishInflate();
inflateEmptyShadeView();
- inflateFooterView();
+ if (!FooterViewRefactor.isEnabled()) {
+ inflateFooterView();
+ }
}
/**
@@ -730,9 +732,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
void reinflateViews() {
- inflateFooterView();
+ if (!FooterViewRefactor.isEnabled()) {
+ inflateFooterView();
+ updateFooter();
+ }
inflateEmptyShadeView();
- updateFooter();
mSectionsManager.reinflateViews();
}
@@ -747,7 +751,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@VisibleForTesting
public void updateFooter() {
- if (mFooterView == null) {
+ if (mFooterView == null || mController == null) {
return;
}
// TODO: move this logic to controller, which will invoke updateFooterView directly
@@ -3144,10 +3148,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
requestChildrenUpdate();
}
- public boolean containsView(View v) {
- return v.getParent() == this;
- }
-
public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
// Modify the clipping for launching notifications
mLaunchAnimationParams = params;
@@ -3410,11 +3410,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mAnimationEvents.add(animEvent);
}
mChildrenChangingPositions.clear();
- if (mGenerateChildOrderChangedEvent) {
- mAnimationEvents.add(new AnimationEvent(null,
- AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
- mGenerateChildOrderChangedEvent = false;
- }
}
private void generateChildAdditionEvents() {
@@ -4556,7 +4551,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return mFooterView != null && mFooterView.isHistoryShown();
}
- void setFooterView(@NonNull FooterView footerView) {
+ /** Bind the {@link FooterView} to the NSSL. */
+ public void setFooterView(@NonNull FooterView footerView) {
int index = -1;
if (mFooterView != null) {
index = indexOfChild(mFooterView);
@@ -4567,6 +4563,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (mManageButtonClickListener != null) {
mFooterView.setManageButtonClickListener(mManageButtonClickListener);
}
+ mFooterView.setClearAllButtonClickListener(v -> {
+ if (mFooterClearAllListener != null) {
+ mFooterClearAllListener.onClearAll();
+ }
+ clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setSecondaryVisible(false /* visible */, true /* animate */);
+ });
+ if (FooterViewRefactor.isEnabled()) {
+ updateFooter();
+ }
}
public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4629,7 +4635,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mFooterView.setVisible(visible, animate);
mFooterView.setSecondaryVisible(showDismissView, animate);
mFooterView.showHistory(showHistory);
- mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+ if (!FooterViewRefactor.isEnabled()) {
+ mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
+ }
}
@VisibleForTesting
@@ -4764,14 +4772,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
info.setClassName(ScrollView.class.getName());
}
- public void generateChildOrderChangedEvent() {
- if (mIsExpanded && mAnimationsEnabled) {
- mGenerateChildOrderChangedEvent = true;
- mNeedsAnimation = true;
- requestChildrenUpdate();
- }
- }
-
public int getContainerChildCount() {
return getChildCount();
}
@@ -5388,15 +5388,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@VisibleForTesting
protected void inflateFooterView() {
+ FooterViewRefactor.assertInLegacyMode();
FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_notification_footer, this, false);
- footerView.setClearAllButtonClickListener(v -> {
- if (mFooterClearAllListener != null) {
- mFooterClearAllListener.onClearAll();
- }
- clearNotifications(ROWS_ALL, true /* closeShade */);
- footerView.setSecondaryVisible(false /* visible */, true /* animate */);
- });
setFooterView(footerView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6a70815f82f3..79448b46fa06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -62,7 +62,7 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
@@ -106,6 +106,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -114,7 +115,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
@@ -194,7 +194,7 @@ public class NotificationStackScrollLayoutController {
private final GroupExpansionManager mGroupExpansionManager;
private final NotifPipelineFlags mNotifPipelineFlags;
- private final NotificationListInteractor mNotificationListInteractor;
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor;
private final KeyguardTransitionRepository mKeyguardTransitionRepo;
private NotificationStackScrollLayout mView;
@@ -206,7 +206,7 @@ public class NotificationStackScrollLayoutController {
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private boolean mIsInTransitionToAod = false;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private final RefactorFlag mShelfRefactor;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private final SecureSettings mSecureSettings;
@@ -662,14 +662,14 @@ public class NotificationStackScrollLayoutController {
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
- NotificationListInteractor notificationListInteractor,
+ SeenNotificationsInteractor seenNotificationsInteractor,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
NotificationStackSizeCalculator notificationStackSizeCalculator,
NotificationIconAreaController notifIconAreaController,
- FeatureFlags featureFlags,
+ FeatureFlagsClassic featureFlags,
NotificationTargetsHelper notificationTargetsHelper,
SecureSettings secureSettings,
NotificationDismissibilityProvider dismissibilityProvider,
@@ -715,7 +715,7 @@ public class NotificationStackScrollLayoutController {
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
- mNotificationListInteractor = notificationListInteractor;
+ mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mNotifIconAreaController = notifIconAreaController;
mFeatureFlags = featureFlags;
@@ -832,7 +832,8 @@ public class NotificationStackScrollLayoutController {
mViewModel.ifPresent(
vm -> NotificationListViewBinder
- .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController));
+ .bind(mView, vm, mFalsingManager, mFeatureFlags, mNotifIconAreaController,
+ mConfigurationController));
collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
this::onKeyguardTransitionChanged);
@@ -1725,28 +1726,11 @@ public class NotificationStackScrollLayoutController {
}
@Override
- public void generateAddAnimation(ExpandableView child, boolean fromMoreCard) {
- mView.generateAddAnimation(child, fromMoreCard);
- }
-
- @Override
- public void generateChildOrderChangedEvent() {
- mView.generateChildOrderChangedEvent();
- }
-
- @Override
public int getContainerChildCount() {
return mView.getContainerChildCount();
}
@Override
- public void setNotificationActivityStarter(
- NotificationActivityStarter notificationActivityStarter) {
- NotificationStackScrollLayoutController.this
- .setNotificationActivityStarter(notificationActivityStarter);
- }
-
- @Override
public int getTopClippingStartLocation() {
return mView.getTopClippingStartLocation();
}
@@ -1841,16 +1825,6 @@ public class NotificationStackScrollLayoutController {
}
@Override
- public boolean containsView(View v) {
- return mView.containsView(v);
- }
-
- @Override
- public void setWillExpand(boolean willExpand) {
- mView.setWillExpand(willExpand);
- }
-
- @Override
public void dumpPipeline(@NonNull PipelineDumper d) {
d.dump("NotificationStackScrollLayoutController.this",
NotificationStackScrollLayoutController.this);
@@ -2006,7 +1980,7 @@ public class NotificationStackScrollLayoutController {
public void setNotifStats(@NonNull NotifStats notifStats) {
mNotifStats = notifStats;
mView.setHasFilteredOutSeenNotifications(
- mNotificationListInteractor.getHasFilteredOutSeenNotifications().getValue());
+ mSeenNotificationsInteractor.getHasFilteredOutSeenNotifications().getValue());
updateFooter();
updateShowEmptyShadeView();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index dee3973edb55..a3792cf6a0f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -17,14 +17,23 @@
package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.view.LayoutInflater
-import com.android.systemui.res.R
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
+import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinder
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel
import com.android.systemui.statusbar.phone.NotificationIconAreaController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.onDensityOrFontScaleChanged
+import com.android.systemui.statusbar.policy.onThemeChanged
+import com.android.systemui.util.traceSection
+import com.android.systemui.util.view.reinflateAndBindLatest
+import kotlinx.coroutines.flow.merge
/** Binds a [NotificationStackScrollLayout] to its [view model][NotificationListViewModel]. */
object NotificationListViewBinder {
@@ -33,8 +42,9 @@ object NotificationListViewBinder {
view: NotificationStackScrollLayout,
viewModel: NotificationListViewModel,
falsingManager: FalsingManager,
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
iconAreaController: NotificationIconAreaController,
+ configurationController: ConfigurationController,
) {
val shelf =
LayoutInflater.from(view.context)
@@ -47,5 +57,26 @@ object NotificationListViewBinder {
iconAreaController
)
view.setShelf(shelf)
+
+ viewModel.footer.ifPresent { footerViewModel ->
+ // The footer needs to be re-inflated every time the theme or the font size changes.
+ view.repeatWhenAttached {
+ LayoutInflater.from(view.context).reinflateAndBindLatest(
+ R.layout.status_bar_notification_footer,
+ view,
+ attachToRoot = false,
+ // TODO(b/305930747): This may lead to duplicate invocations if both flows emit,
+ // find a solution to only emit one event.
+ merge(
+ configurationController.onThemeChanged,
+ configurationController.onDensityOrFontScaleChanged,
+ ),
+ ) { view ->
+ traceSection("bind FooterView") {
+ FooterViewBinder.bind(view as FooterView, footerViewModel)
+ }
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 11f68e0f663b..261371d59a3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -17,8 +17,9 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import dagger.Module
import dagger.Provides
@@ -28,6 +29,7 @@ import javax.inject.Provider
/** ViewModel for the list of notifications. */
class NotificationListViewModel(
val shelf: NotificationShelfViewModel,
+ val footer: Optional<FooterViewModel>,
)
@Module
@@ -36,12 +38,33 @@ object NotificationListViewModelModule {
@Provides
@SysUISingleton
fun maybeProvideViewModel(
- featureFlags: FeatureFlags,
+ featureFlags: FeatureFlagsClassic,
shelfViewModel: Provider<NotificationShelfViewModel>,
- ): Optional<NotificationListViewModel> =
- if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
- Optional.of(NotificationListViewModel(shelfViewModel.get()))
+ footerViewModel: Provider<FooterViewModel>,
+ ): Optional<NotificationListViewModel> {
+ return if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+ Optional.of(
+ NotificationListViewModel(
+ shelfViewModel.get(),
+ Optional.of(footerViewModel.get())
+ )
+ )
+ } else {
+ Optional.of(NotificationListViewModel(shelfViewModel.get(), Optional.empty()))
+ }
} else {
+ if (com.android.systemui.Flags.notificationsFooterViewRefactor()) {
+ throw IllegalStateException(
+ "The com.android.systemui.notifications_footer_view_refactor flag requires " +
+ "the notification_shelf_refactor flag to be enabled. First disable the " +
+ "footer flag using `adb shell device_config put systemui " +
+ "com.android.systemui.notifications_footer_view_refactor false`, then " +
+ "enable the notification_shelf_refactor flag in Flag Flipper. " +
+ "Afterwards, you can try re-enabling the footer refactor flag via adb."
+ )
+ }
Optional.empty()
}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 07d3a1cf24c0..2d125462b16e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -30,7 +30,6 @@ import android.view.View
import android.view.WindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
-import com.android.systemui.res.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.ActivityLaunchAnimator.PendingIntentStarter
import com.android.systemui.animation.DelegateLaunchAnimatorController
@@ -43,6 +42,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
@@ -134,6 +134,19 @@ constructor(
)
}
+ override fun startPendingIntentMaybeDismissingKeyguard(
+ intent: PendingIntent,
+ intentSentUiThreadCallback: Runnable?,
+ animationController: ActivityLaunchAnimator.Controller?
+ ) {
+ activityStarterInternal.startPendingIntentDismissingKeyguard(
+ intent = intent,
+ intentSentUiThreadCallback = intentSentUiThreadCallback,
+ animationController = animationController,
+ showOverLockscreen = true,
+ )
+ }
+
/**
* TODO(b/279084380): Change callers to just call startActivityDismissingKeyguard and deprecate
* this.
@@ -454,7 +467,7 @@ constructor(
!willLaunchResolverActivity &&
shouldAnimateLaunch(isActivityIntent = true)
val animController =
- wrapAnimationController(
+ wrapAnimationControllerForShadeOrStatusBar(
animationController = animationController,
dismissShade = dismissShade,
isLaunchForActivity = true,
@@ -547,12 +560,18 @@ constructor(
)
}
- /** Starts a pending intent after dismissing keyguard. */
+ /**
+ * Starts a pending intent after dismissing keyguard.
+ *
+ * This can be called in a background thread (to prevent calls in [ActivityIntentHelper] in
+ * the main thread).
+ */
fun startPendingIntentDismissingKeyguard(
intent: PendingIntent,
intentSentUiThreadCallback: Runnable? = null,
associatedView: View? = null,
animationController: ActivityLaunchAnimator.Controller? = null,
+ showOverLockscreen: Boolean = false,
) {
val animationController =
if (associatedView is ExpandableNotificationRow) {
@@ -566,79 +585,103 @@ constructor(
lockScreenUserManager.currentUserId,
))
+ val actuallyShowOverLockscreen =
+ showOverLockscreen &&
+ intent.isActivity &&
+ activityIntentHelper.wouldPendingShowOverLockscreen(
+ intent,
+ lockScreenUserManager.currentUserId
+ )
+
val animate =
!willLaunchResolverActivity &&
animationController != null &&
- shouldAnimateLaunch(intent.isActivity)
+ shouldAnimateLaunch(intent.isActivity, actuallyShowOverLockscreen)
+
+ // We wrap animationCallback with a StatusBarLaunchAnimatorController so
+ // that the shade is collapsed after the animation (or when it is cancelled,
+ // aborted, etc).
+ val statusBarController =
+ wrapAnimationControllerForShadeOrStatusBar(
+ animationController = animationController,
+ dismissShade = true,
+ isLaunchForActivity = intent.isActivity,
+ )
+ val controller =
+ if (actuallyShowOverLockscreen) {
+ wrapAnimationControllerForLockscreen(statusBarController)
+ } else {
+ statusBarController
+ }
// If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
// run the animation on the keyguard). The animation will take care of (instantly)
// collapsing the shade and hiding the keyguard once it is done.
val collapse = !animate
- executeRunnableDismissingKeyguard(
- runnable = {
- try {
- // We wrap animationCallback with a StatusBarLaunchAnimatorController so
- // that the shade is collapsed after the animation (or when it is cancelled,
- // aborted, etc).
- val controller: ActivityLaunchAnimator.Controller? =
- wrapAnimationController(
- animationController = animationController,
- dismissShade = true,
- isLaunchForActivity = intent.isActivity,
- )
- activityLaunchAnimator.startPendingIntentWithAnimation(
- controller,
- animate,
- intent.creatorPackage,
- object : PendingIntentStarter {
- override fun startPendingIntent(
- animationAdapter: RemoteAnimationAdapter?
- ): Int {
- val options =
- ActivityOptions(
- CentralSurfaces.getActivityOptions(
- displayId,
- animationAdapter
- )
+ val runnable = Runnable {
+ try {
+ activityLaunchAnimator.startPendingIntentWithAnimation(
+ controller,
+ animate,
+ intent.creatorPackage,
+ actuallyShowOverLockscreen,
+ object : PendingIntentStarter {
+ override fun startPendingIntent(
+ animationAdapter: RemoteAnimationAdapter?
+ ): Int {
+ val options =
+ ActivityOptions(
+ CentralSurfaces.getActivityOptions(
+ displayId,
+ animationAdapter
)
- // TODO b/221255671: restrict this to only be set for
- // notifications
- options.isEligibleForLegacyPermissionPrompt = true
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
- return intent.sendAndReturnResult(
- null,
- 0,
- null,
- null,
- null,
- null,
- options.toBundle()
- )
- }
- },
- )
- } catch (e: PendingIntent.CanceledException) {
- // the stack trace isn't very helpful here.
- // Just log the exception message.
- Log.w(TAG, "Sending intent failed: $e")
- if (!collapse) {
- // executeRunnableDismissingKeyguard did not collapse for us already.
- shadeControllerLazy.get().collapseOnMainThread()
- }
- // TODO: Dismiss Keyguard.
- }
- if (intent.isActivity) {
- assistManagerLazy.get().hideAssist()
+ // TODO b/221255671: restrict this to only be set for
+ // notifications
+ options.isEligibleForLegacyPermissionPrompt = true
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ return intent.sendAndReturnResult(
+ null,
+ 0,
+ null,
+ null,
+ null,
+ null,
+ options.toBundle()
+ )
+ }
+ },
+ )
+ } catch (e: PendingIntent.CanceledException) {
+ // the stack trace isn't very helpful here.
+ // Just log the exception message.
+ Log.w(TAG, "Sending intent failed: $e")
+ if (!collapse) {
+ // executeRunnableDismissingKeyguard did not collapse for us already.
+ shadeControllerLazy.get().collapseOnMainThread()
}
- intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
- },
- afterKeyguardGone = willLaunchResolverActivity,
- dismissShade = collapse,
- willAnimateOnKeyguard = animate,
- )
+ // TODO: Dismiss Keyguard.
+ }
+ if (intent.isActivity) {
+ assistManagerLazy.get().hideAssist()
+ }
+ intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
+ }
+
+ if (!actuallyShowOverLockscreen) {
+ postOnUiThread(delay = 0) {
+ executeRunnableDismissingKeyguard(
+ runnable = runnable,
+ afterKeyguardGone = willLaunchResolverActivity,
+ dismissShade = collapse,
+ willAnimateOnKeyguard = animate,
+ )
+ }
+ } else {
+ postOnUiThread(delay = 0, runnable)
+ }
}
/** Starts an Activity. */
@@ -678,71 +721,12 @@ constructor(
// Wrap the animation controller to dismiss the shade and set
// mIsLaunchingActivityOverLockscreen during the animation.
val delegate =
- wrapAnimationController(
+ wrapAnimationControllerForShadeOrStatusBar(
animationController = animationController,
dismissShade = dismissShade,
isLaunchForActivity = true,
)
- delegate?.let {
- controller =
- object : DelegateLaunchAnimatorController(delegate) {
- override fun onIntentStarted(willAnimate: Boolean) {
- delegate?.onIntentStarted(willAnimate)
- if (willAnimate) {
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
- }
- }
-
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- super.onLaunchAnimationStart(isExpandingFullyAbove)
-
- // Double check that the keyguard is still showing and not going
- // away, but if so set the keyguard occluded. Typically, WM will let
- // KeyguardViewMediator know directly, but we're overriding that to
- // play the custom launch animation, so we need to take care of that
- // here. The unocclude animation is not overridden, so WM will call
- // KeyguardViewMediator's unocclude animation runner when the
- // activity is exited.
- if (
- keyguardStateController.isShowing &&
- !keyguardStateController.isKeyguardGoingAway
- ) {
- Log.d(TAG, "Setting occluded = true in #startActivity.")
- keyguardViewMediatorLazy
- .get()
- .setOccluded(true /* isOccluded */, true /* animate */)
- }
- }
-
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- // Set mIsLaunchingActivityOverLockscreen to false before actually
- // finishing the animation so that we can assume that
- // mIsLaunchingActivityOverLockscreen being true means that we will
- // collapse the shade (or at least run the post collapse runnables)
- // later on.
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate?.onLaunchAnimationEnd(isExpandingFullyAbove)
- }
-
- override fun onLaunchAnimationCancelled(
- newKeyguardOccludedState: Boolean?
- ) {
- if (newKeyguardOccludedState != null) {
- keyguardViewMediatorLazy
- .get()
- .setOccluded(newKeyguardOccludedState, false /* animate */)
- }
-
- // Set mIsLaunchingActivityOverLockscreen to false before actually
- // finishing the animation so that we can assume that
- // mIsLaunchingActivityOverLockscreen being true means that we will
- // collapse the shade (or at least run the // post collapse
- // runnables) later on.
- centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
- delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
- }
- }
- }
+ controller = wrapAnimationControllerForLockscreen(delegate)
} else if (dismissShade) {
// The animation will take care of dismissing the shade at the end of the animation.
// If we don't animate, collapse it directly.
@@ -874,7 +858,7 @@ constructor(
* window.
* @param isLaunchForActivity whether the launch is for an activity.
*/
- private fun wrapAnimationController(
+ private fun wrapAnimationControllerForShadeOrStatusBar(
animationController: ActivityLaunchAnimator.Controller?,
dismissShade: Boolean,
isLaunchForActivity: Boolean,
@@ -909,6 +893,72 @@ constructor(
return animationController
}
+ /**
+ * Wraps an animation controller so that if an activity would be launched on top of the
+ * lockscreen, the correct flags are set for it to be occluded.
+ */
+ private fun wrapAnimationControllerForLockscreen(
+ animationController: ActivityLaunchAnimator.Controller?
+ ): ActivityLaunchAnimator.Controller? {
+ return animationController?.let {
+ object : DelegateLaunchAnimatorController(it) {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ delegate.onIntentStarted(willAnimate)
+ if (willAnimate) {
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(true)
+ }
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ super.onLaunchAnimationStart(isExpandingFullyAbove)
+
+ // Double check that the keyguard is still showing and not going
+ // away, but if so set the keyguard occluded. Typically, WM will let
+ // KeyguardViewMediator know directly, but we're overriding that to
+ // play the custom launch animation, so we need to take care of that
+ // here. The unocclude animation is not overridden, so WM will call
+ // KeyguardViewMediator's unocclude animation runner when the
+ // activity is exited.
+ if (
+ keyguardStateController.isShowing &&
+ !keyguardStateController.isKeyguardGoingAway
+ ) {
+ Log.d(TAG, "Setting occluded = true in #startActivity.")
+ keyguardViewMediatorLazy
+ .get()
+ .setOccluded(true /* isOccluded */, true /* animate */)
+ }
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the post collapse runnables)
+ // later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+ if (newKeyguardOccludedState != null) {
+ keyguardViewMediatorLazy
+ .get()
+ .setOccluded(newKeyguardOccludedState, false /* animate */)
+ }
+
+ // Set mIsLaunchingActivityOverLockscreen to false before actually
+ // finishing the animation so that we can assume that
+ // mIsLaunchingActivityOverLockscreen being true means that we will
+ // collapse the shade (or at least run the // post collapse
+ // runnables) later on.
+ centralSurfaces?.setIsLaunchingActivityOverLockscreen(false)
+ delegate.onLaunchAnimationCancelled(newKeyguardOccludedState)
+ }
+ }
+ }
+ }
+
/** Retrieves the current user handle to start the Activity. */
private fun getActivityUserHandle(intent: Intent): UserHandle {
val packages: Array<String> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 59f10aed4145..daa4f1807625 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -511,7 +511,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
case MODE_WAKE_AND_UNLOCK:
if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
- mMediaManager.updateMediaMetaData(false /* metaDataChanged */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
} else {
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 9fb6c1bb2fd0..fa2e3a476bda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -76,7 +76,6 @@ import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.Log;
-import android.util.MathUtils;
import android.view.Display;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowManager;
@@ -176,7 +175,6 @@ import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.statusbar.AutoHideUiElement;
-import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
@@ -204,7 +202,6 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -237,10 +234,10 @@ import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
import com.android.wm.shell.startingsurface.StartingSurface;
-import dagger.Lazy;
-
import dalvik.annotation.optimization.NeverCompile;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
@@ -298,7 +295,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
private final NotificationListContainer mNotifListContainer;
- private final NotificationExpansionRepository mNotificationExpansionRepository;
private boolean mIsShortcutListSearchEnabled;
private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -376,9 +372,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private boolean mBrightnessMirrorVisible;
private BiometricUnlockController mBiometricUnlockController;
private final LightBarController mLightBarController;
- private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
- @Nullable
- protected LockscreenWallpaper mLockscreenWallpaper;
private final AutoHideController mAutoHideController;
private final Point mCurrentDisplaySize = new Point();
@@ -655,10 +648,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
Lazy<NotificationPresenter> notificationPresenterLazy,
Lazy<NotificationActivityStarter> notificationActivityStarterLazy,
NotificationLaunchAnimatorControllerProvider notifLaunchAnimatorControllerProvider,
- NotificationExpansionRepository notificationExpansionRepository,
DozeParameters dozeParameters,
ScrimController scrimController,
- Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
AuthRippleController authRippleController,
DozeServiceHost dozeServiceHost,
@@ -765,12 +756,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mPresenterLazy = notificationPresenterLazy;
mNotificationActivityStarterLazy = notificationActivityStarterLazy;
mNotificationAnimationProvider = notifLaunchAnimatorControllerProvider;
- mNotificationExpansionRepository = notificationExpansionRepository;
mDozeServiceHost = dozeServiceHost;
mPowerManager = powerManager;
mDozeParameters = dozeParameters;
mScrimController = scrimController;
- mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
mDozeScrimController = dozeScrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mAuthRippleController = authRippleController;
@@ -1198,10 +1187,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
createNavigationBar(result);
- if (ENABLE_LOCKSCREEN_WALLPAPER && mWallpaperSupported) {
- mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
- }
-
mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
R.id.ambient_indication_container);
@@ -1268,24 +1253,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mNotificationShelfController,
mHeadsUpManager);
- BackDropView backdrop = getNotificationShadeWindowView().findViewById(R.id.backdrop);
- if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
- mMediaManager.setup(null, null, null, mScrimController, null);
- } else {
- mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
- backdrop.findViewById(R.id.backdrop_back), mScrimController,
- mLockscreenWallpaper);
- }
- float maxWallpaperZoom = mContext.getResources().getFloat(
- com.android.internal.R.dimen.config_wallpaperMaxScale);
- mNotificationShadeDepthControllerLazy.get().addListener(depth -> {
- float scale = MathUtils.lerp(maxWallpaperZoom, 1f, depth);
- backdrop.setPivotX(backdrop.getWidth() / 2f);
- backdrop.setPivotY(backdrop.getHeight() / 2f);
- backdrop.setScaleX(scale);
- backdrop.setScaleY(scale);
- });
-
// Set up the quick settings tile panel
final View container = getNotificationShadeWindowView().findViewById(R.id.qs_frame);
if (container != null) {
@@ -1357,14 +1324,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// receive broadcasts
registerBroadcastReceiver();
- IntentFilter demoFilter = new IntentFilter();
- if (DEBUG_MEDIA_FAKE_ARTWORK) {
- demoFilter.addAction(ACTION_FAKE_ARTWORK);
- }
- mContext.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
- android.Manifest.permission.DUMP, null,
- Context.RECEIVER_EXPORTED_UNAUDITED);
-
// listen for USER_SETUP_COMPLETE setting (per-user)
mDeviceProvisionedController.addCallback(mUserSetupObserver);
mUserSetupObserver.onUserSetupChanged();
@@ -1525,6 +1484,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// regressions, we'll continue standing up the root view in CentralSurfaces.
mNotificationShadeWindowController.fetchWindowRootView();
getNotificationShadeWindowViewController().setupExpandedStatusBar();
+ getNotificationShadeWindowViewController().setupCommunalHubLayout();
mShadeController.setNotificationShadeWindowViewController(
getNotificationShadeWindowViewController());
mBackActionInteractor.setup(mQsController, mShadeSurface);
@@ -1582,7 +1542,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
- mMediaManager.setBiometricUnlockController(mBiometricUnlockController);
Trace.endSection();
}
@@ -1869,7 +1828,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
void updateDisplaySize() {
mDisplay.getMetrics(mDisplayMetrics);
mDisplay.getSize(mCurrentDisplaySize);
- mMediaManager.onDisplayUpdated(mDisplay);
if (DEBUG_GESTURES) {
mGestureRec.tag("display",
String.format("%dx%d", mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
@@ -1943,19 +1901,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
};
- private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) Log.v(TAG, "onReceive: " + intent);
- String action = intent.getAction();
- if (ACTION_FAKE_ARTWORK.equals(action)) {
- if (DEBUG_MEDIA_FAKE_ARTWORK) {
- mPresenterLazy.get().updateMediaMetaData(true, true);
- }
- }
- }
- };
-
/**
* Reload some of our resources when the configuration changes.
*
@@ -2138,7 +2083,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mKeyguardStateController.setLaunchTransitionFadingAway(false);
- mPresenterLazy.get().updateMediaMetaData(true /* metaDataChanged */, true);
}
/**
@@ -2162,7 +2106,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
beforeFading.run();
}
updateScrimController();
- mPresenterLazy.get().updateMediaMetaData(false, true);
mShadeSurface.resetAlpha();
mShadeSurface.fadeOut(
FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
@@ -3159,7 +3102,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
// TODO: Bring these out of CentralSurfaces.
mUserInfoControllerImpl.onDensityOrFontScaleChanged();
- mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext);
+ }
}
@Override
@@ -3177,7 +3122,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
}
- mNotificationIconAreaController.onThemeChanged();
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mNotificationIconAreaController.onThemeChanged();
+ }
}
@Override
@@ -3221,8 +3168,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
updateDozingState();
checkBarModes();
updateScrimController();
- mPresenterLazy.get()
- .updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index d3d11ea4a9f3..66341ba42a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -37,6 +37,8 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -82,6 +84,7 @@ public final class DozeServiceHost implements DozeHost {
private final SysuiStatusBarStateController mStatusBarStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final HeadsUpManager mHeadsUpManager;
+ private final FeatureFlagsClassic mFeatureFlags;
private final BatteryController mBatteryController;
private final ScrimController mScrimController;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -107,6 +110,7 @@ public final class DozeServiceHost implements DozeHost {
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
DeviceProvisionedController deviceProvisionedController,
+ FeatureFlagsClassic featureFlags,
HeadsUpManager headsUpManager, BatteryController batteryController,
ScrimController scrimController,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
@@ -130,6 +134,7 @@ public final class DozeServiceHost implements DozeHost {
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mAssistManagerLazy = assistManagerLazy;
mDozeScrimController = dozeScrimController;
+ mFeatureFlags = featureFlags;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPulseExpansionHandler = pulseExpansionHandler;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -173,8 +178,13 @@ public final class DozeServiceHost implements DozeHost {
void fireNotificationPulse(NotificationEntry entry) {
Runnable pulseSuppressedListener = () -> {
- entry.setPulseSuppressed(true);
- mNotificationIconAreaController.updateAodNotificationIcons();
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpManager.removeNotification(
+ entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
+ } else {
+ entry.setPulseSuppressed(true);
+ mNotificationIconAreaController.updateAodNotificationIcons();
+ }
};
Assert.isMainThread();
for (Callback callback : mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index c493eeda7077..8fee5c0487f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -26,6 +26,8 @@ import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -38,6 +40,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -104,6 +107,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
};
private boolean mAnimationsEnabled = true;
private final KeyguardStateController mKeyguardStateController;
+ private final FeatureFlagsClassic mFeatureFlags;
+ private final HeadsUpNotificationIconInteractor mHeadsUpNotificationIconInteractor;
@VisibleForTesting
@Inject
@@ -122,6 +127,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
NotificationRoundnessManager notificationRoundnessManager,
HeadsUpStatusBarView headsUpStatusBarView,
Clock clockView,
+ FeatureFlagsClassic featureFlags,
+ HeadsUpNotificationIconInteractor headsUpNotificationIconInteractor,
@Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
mNotificationIconAreaController = notificationIconAreaController;
@@ -139,6 +146,8 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
mStackScrollerController = stackScrollerController;
mShadeViewController = shadeViewController;
+ mFeatureFlags = featureFlags;
+ mHeadsUpNotificationIconInteractor = headsUpNotificationIconInteractor;
mStackScrollerController.setHeadsUpAppearanceController(this);
mClockView = clockView;
mOperatorNameViewOptional = operatorNameViewOptional;
@@ -170,6 +179,9 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
mHeadsUpManager.addListener(this);
mView.setOnDrawingRectChangedListener(
() -> updateIsolatedIconLocation(true /* requireUpdate */));
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ updateIsolatedIconLocation(true);
+ }
mWakeUpCoordinator.addListener(this);
getShadeHeadsUpTracker().addTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(this);
@@ -185,6 +197,9 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
protected void onViewDetached() {
mHeadsUpManager.removeListener(this);
mView.setOnDrawingRectChangedListener(null);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpNotificationIconInteractor.setIsolatedIconLocation(null);
+ }
mWakeUpCoordinator.removeListener(this);
getShadeHeadsUpTracker().removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
getShadeHeadsUpTracker().setHeadsUpAppearanceController(null);
@@ -193,8 +208,13 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
}
private void updateIsolatedIconLocation(boolean requireStateUpdate) {
- mNotificationIconAreaController.setIsolatedIconLocation(
- mView.getIconDrawingRect(), requireStateUpdate);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpNotificationIconInteractor
+ .setIsolatedIconLocation(mView.getIconDrawingRect());
+ } else {
+ mNotificationIconAreaController.setIsolatedIconLocation(
+ mView.getIconDrawingRect(), requireStateUpdate);
+ }
}
@Override
@@ -230,9 +250,14 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar
setShown(true);
animateIsolation = !isExpanded();
}
- updateIsolatedIconLocation(false /* requireUpdate */);
- mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
- : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mHeadsUpNotificationIconInteractor.setIsolatedIconNotificationKey(
+ newEntry == null ? null : newEntry.getKey());
+ } else {
+ updateIsolatedIconLocation(false /* requireUpdate */);
+ mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
+ : newEntry.getIcons().getStatusBarIcon(), animateIsolation);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 6b4382f731ea..f4862c73606f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -175,7 +175,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
if (!hasPinnedHeadsUp() || topEntry == null) {
return null;
} else {
- if (topEntry.isChildInGroup()) {
+ if (topEntry.rowIsChildInGroup()) {
final NotificationEntry groupSummary =
mGroupMembershipManager.getGroupSummary(topEntry);
if (groupSummary != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 2960520f00b4..2206be5e614b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -546,8 +546,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
* (1.0f - mKeyguardHeadsUpShowingAmount);
}
- if (mSystemEventAnimator.isAnimationRunning()
- && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
+ if (mSystemEventAnimator.isAnimationRunning()) {
newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha);
} else {
mView.setTranslationX(0);
@@ -704,21 +703,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private StatusBarSystemEventDefaultAnimator getSystemEventAnimator(boolean isAnimationRunning) {
return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
- // TODO(b/273443374): remove if-else condition
- if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
- mSystemEventAnimatorAlpha = alpha;
- } else {
- mSystemEventAnimatorAlpha = 1f;
- }
+ mSystemEventAnimatorAlpha = alpha;
updateViewState();
return Unit.INSTANCE;
}, (translationX) -> {
- // TODO(b/273443374): remove if-else condition
- if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) {
- mView.setTranslationX(translationX);
- } else {
- mView.setTranslationX(0);
- }
+ mView.setTranslationX(translationX);
return Unit.INSTANCE;
}, isAnimationRunning);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
index f9856b0415e8..4284c96c9966 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImpl.java
@@ -448,7 +448,7 @@ public class LegacyNotificationIconAreaControllerImpl implements
}
}
replacingIcons.removeAll(duplicates);
- hostLayout.setReplacingIcons(replacingIcons);
+ hostLayout.setReplacingIconsLegacy(replacingIcons);
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
deleted file mode 100644
index 00fd9fbfffe3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ /dev/null
@@ -1,434 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.Nullable;
-import android.app.IWallpaperManager;
-import android.app.IWallpaperManagerCallback;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Rect;
-import android.graphics.Xfermode;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableWrapper;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.ParcelFileDescriptor;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.user.data.model.SelectedUserModel;
-import com.android.systemui.user.data.model.SelectionStatus;
-import com.android.systemui.user.data.repository.UserRepository;
-import com.android.systemui.util.kotlin.JavaAdapter;
-
-import libcore.io.IoUtils;
-
-import java.io.PrintWriter;
-import java.util.Objects;
-
-import javax.inject.Inject;
-
-/**
- * Manages the lockscreen wallpaper.
- */
-@SysUISingleton
-public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable,
- Dumpable, CoreStartable {
-
- private static final String TAG = "LockscreenWallpaper";
-
- // TODO(b/253507223): temporary; remove this
- private static final String DISABLED_ERROR_MESSAGE = "Methods from LockscreenWallpaper.java "
- + "should not be called in this version. The lock screen wallpaper should be "
- + "managed by the WallpaperManagerService and not by this class.";
-
- private final NotificationMediaManager mMediaManager;
- private final WallpaperManager mWallpaperManager;
- private final KeyguardUpdateMonitor mUpdateMonitor;
- private final Handler mH;
- private final JavaAdapter mJavaAdapter;
- private final UserRepository mUserRepository;
-
- private boolean mCached;
- private Bitmap mCache;
- private int mCurrentUserId;
- // The user selected in the UI, or null if no user is selected or UI doesn't support selecting
- // users.
- private UserHandle mSelectedUser;
- private AsyncTask<Void, Void, LoaderResult> mLoader;
-
- @Inject
- public LockscreenWallpaper(WallpaperManager wallpaperManager,
- @Nullable IWallpaperManager iWallpaperManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- DumpManager dumpManager,
- NotificationMediaManager mediaManager,
- @Main Handler mainHandler,
- JavaAdapter javaAdapter,
- UserRepository userRepository,
- UserTracker userTracker) {
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
- mWallpaperManager = wallpaperManager;
- mCurrentUserId = userTracker.getUserId();
- mUpdateMonitor = keyguardUpdateMonitor;
- mMediaManager = mediaManager;
- mH = mainHandler;
- mJavaAdapter = javaAdapter;
- mUserRepository = userRepository;
-
- if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
- // Service is disabled on some devices like Automotive
- try {
- iWallpaperManager.setLockWallpaperCallback(this);
- } catch (RemoteException e) {
- Log.e(TAG, "System dead?" + e);
- }
- }
- }
-
- @Override
- public void start() {
- if (!isLockscreenLiveWallpaperEnabled()) {
- mJavaAdapter.alwaysCollectFlow(
- mUserRepository.getSelectedUser(), this::setSelectedUser);
- }
- }
-
- public Bitmap getBitmap() {
- assertLockscreenLiveWallpaperNotEnabled();
-
- if (mCached) {
- return mCache;
- }
- if (!mWallpaperManager.isWallpaperSupported()) {
- mCached = true;
- mCache = null;
- return null;
- }
-
- LoaderResult result = loadBitmap(mCurrentUserId, mSelectedUser);
- if (result.success) {
- mCached = true;
- mCache = result.bitmap;
- }
- return mCache;
- }
-
- public LoaderResult loadBitmap(int currentUserId, UserHandle selectedUser) {
- // May be called on any thread - only use thread safe operations.
-
- assertLockscreenLiveWallpaperNotEnabled();
-
-
- if (!mWallpaperManager.isWallpaperSupported()) {
- // When wallpaper is not supported, show the system wallpaper
- return LoaderResult.success(null);
- }
-
- // Prefer the selected user (when specified) over the current user for the FLAG_SET_LOCK
- // wallpaper.
- final int lockWallpaperUserId =
- selectedUser != null ? selectedUser.getIdentifier() : currentUserId;
- ParcelFileDescriptor fd = mWallpaperManager.getWallpaperFile(
- WallpaperManager.FLAG_LOCK, lockWallpaperUserId);
-
- if (fd != null) {
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inPreferredConfig = Bitmap.Config.HARDWARE;
- return LoaderResult.success(BitmapFactory.decodeFileDescriptor(
- fd.getFileDescriptor(), null, options));
- } catch (OutOfMemoryError e) {
- Log.w(TAG, "Can't decode file", e);
- return LoaderResult.fail();
- } finally {
- IoUtils.closeQuietly(fd);
- }
- } else {
- if (selectedUser != null) {
- // Show the selected user's static wallpaper.
- return LoaderResult.success(mWallpaperManager.getBitmapAsUser(
- selectedUser.getIdentifier(), true /* hardware */));
-
- } else {
- // When there is no selected user, show the system wallpaper
- return LoaderResult.success(null);
- }
- }
- }
-
- private void setSelectedUser(SelectedUserModel selectedUserModel) {
- assertLockscreenLiveWallpaperNotEnabled();
-
- if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) {
- // Wait until the selection has finished before updating.
- return;
- }
-
- int user = selectedUserModel.getUserInfo().id;
- if (user != mCurrentUserId) {
- if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) {
- mCached = false;
- }
- mCurrentUserId = user;
- }
- }
-
- public void setSelectedUser(UserHandle selectedUser) {
- assertLockscreenLiveWallpaperNotEnabled();
-
- if (Objects.equals(selectedUser, mSelectedUser)) {
- return;
- }
- mSelectedUser = selectedUser;
- postUpdateWallpaper();
- }
-
- @Override
- public void onWallpaperChanged() {
- assertLockscreenLiveWallpaperNotEnabled();
- // Called on Binder thread.
- postUpdateWallpaper();
- }
-
- @Override
- public void onWallpaperColorsChanged(WallpaperColors colors, int which, int userId) {
- assertLockscreenLiveWallpaperNotEnabled();
- }
-
- private void postUpdateWallpaper() {
- assertLockscreenLiveWallpaperNotEnabled();
- if (mH == null) {
- Log.wtfStack(TAG, "Trying to use LockscreenWallpaper before initialization.");
- return;
- }
- mH.removeCallbacks(this);
- mH.post(this);
- }
- @Override
- public void run() {
- // Called in response to onWallpaperChanged on the main thread.
-
- assertLockscreenLiveWallpaperNotEnabled();
-
- if (mLoader != null) {
- mLoader.cancel(false /* interrupt */);
- }
-
- final int currentUser = mCurrentUserId;
- final UserHandle selectedUser = mSelectedUser;
- mLoader = new AsyncTask<Void, Void, LoaderResult>() {
- @Override
- protected LoaderResult doInBackground(Void... params) {
- return loadBitmap(currentUser, selectedUser);
- }
-
- @Override
- protected void onPostExecute(LoaderResult result) {
- super.onPostExecute(result);
- if (isCancelled()) {
- return;
- }
- if (result.success) {
- mCached = true;
- mCache = result.bitmap;
- mMediaManager.updateMediaMetaData(true /* metaDataChanged */);
- }
- mLoader = null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
-
- // TODO(b/273443374): remove
- public boolean isLockscreenLiveWallpaperEnabled() {
- return mWallpaperManager.isLockscreenLiveWallpaperEnabled();
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println(getClass().getSimpleName() + ":");
- IndentingPrintWriter iPw = new IndentingPrintWriter(pw, " ").increaseIndent();
- iPw.println("mCached=" + mCached);
- iPw.println("mCache=" + mCache);
- iPw.println("mCurrentUserId=" + mCurrentUserId);
- iPw.println("mSelectedUser=" + mSelectedUser);
- }
-
- private static class LoaderResult {
- public final boolean success;
- public final Bitmap bitmap;
-
- LoaderResult(boolean success, Bitmap bitmap) {
- this.success = success;
- this.bitmap = bitmap;
- }
-
- static LoaderResult success(Bitmap b) {
- return new LoaderResult(true, b);
- }
-
- static LoaderResult fail() {
- return new LoaderResult(false, null);
- }
- }
-
- /**
- * Drawable that aligns left horizontally and center vertically (like ImageWallpaper).
- *
- * <p>Aligns to the center when showing on the smaller internal display of a multi display
- * device.
- */
- public static class WallpaperDrawable extends DrawableWrapper {
-
- private final ConstantState mState;
- private final Rect mTmpRect = new Rect();
- private boolean mIsOnSmallerInternalDisplays;
-
- public WallpaperDrawable(Resources r, Bitmap b, boolean isOnSmallerInternalDisplays) {
- this(r, new ConstantState(b), isOnSmallerInternalDisplays);
- }
-
- private WallpaperDrawable(Resources r, ConstantState state,
- boolean isOnSmallerInternalDisplays) {
- super(new BitmapDrawable(r, state.mBackground));
- mState = state;
- mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
- }
-
- @Override
- public void setXfermode(@Nullable Xfermode mode) {
- // DrawableWrapper does not call this for us.
- getDrawable().setXfermode(mode);
- }
-
- @Override
- public int getIntrinsicWidth() {
- return -1;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return -1;
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- int vwidth = getBounds().width();
- int vheight = getBounds().height();
- int dwidth = mState.mBackground.getWidth();
- int dheight = mState.mBackground.getHeight();
- float scale;
- float dx = 0, dy = 0;
-
- if (dwidth * vheight > vwidth * dheight) {
- scale = (float) vheight / (float) dheight;
- } else {
- scale = (float) vwidth / (float) dwidth;
- }
-
- if (scale <= 1f) {
- scale = 1f;
- }
- dy = (vheight - dheight * scale) * 0.5f;
-
- int offsetX = 0;
- // Offset to show the center area of the wallpaper on a smaller display for multi
- // display device
- if (mIsOnSmallerInternalDisplays) {
- offsetX = bounds.centerX() - (Math.round(dwidth * scale) / 2);
- }
-
- mTmpRect.set(
- bounds.left + offsetX,
- bounds.top + Math.round(dy),
- bounds.left + Math.round(dwidth * scale) + offsetX,
- bounds.top + Math.round(dheight * scale + dy));
-
- super.onBoundsChange(mTmpRect);
- }
-
- @Override
- public ConstantState getConstantState() {
- return mState;
- }
-
- /**
- * Update bounds when the hosting display or the display size has changed.
- *
- * @param isOnSmallerInternalDisplays true if the drawable is on one of the internal
- * displays with the smaller area.
- */
- public void onDisplayUpdated(boolean isOnSmallerInternalDisplays) {
- mIsOnSmallerInternalDisplays = isOnSmallerInternalDisplays;
- onBoundsChange(getBounds());
- }
-
- static class ConstantState extends Drawable.ConstantState {
-
- private final Bitmap mBackground;
-
- ConstantState(Bitmap background) {
- mBackground = background;
- }
-
- @Override
- public Drawable newDrawable() {
- return newDrawable(null);
- }
-
- @Override
- public Drawable newDrawable(@Nullable Resources res) {
- return new WallpaperDrawable(res, this, /* isOnSmallerInternalDisplays= */ false);
- }
-
- @Override
- public int getChangingConfigurations() {
- // DrawableWrapper already handles this for us.
- return 0;
- }
- }
- }
-
- /**
- * Feature b/253507223 will adapt the logic to always use the
- * WallpaperManagerService to render the lock screen wallpaper.
- * Methods of this class should not be called at all if the project flag is enabled.
- * TODO(b/253507223) temporary assertion; remove this
- */
- private void assertLockscreenLiveWallpaperNotEnabled() {
- if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
- throw new IllegalStateException(DISABLED_ERROR_MESSAGE);
- }
- }
-}
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 b15c0fdd5a4c..535f6acab5be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -40,6 +40,8 @@ import androidx.collection.ArrayMap;
import com.android.app.animation.Interpolators;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.settingslib.Utils;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
@@ -131,6 +133,9 @@ public class NotificationIconContainer extends ViewGroup {
}
}.setDuration(CONTENT_FADE_DURATION);
+ private final RefactorFlag mIconContainerRefactorFlag =
+ RefactorFlag.forView(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
+
/* Maximum number of icons on AOD when also showing overflow dot. */
private int mMaxIconsOnAod;
@@ -156,7 +161,8 @@ public class NotificationIconContainer extends ViewGroup {
private int mIconSize;
private boolean mDisallowNextAnimation;
private boolean mAnimationsEnabled = true;
- private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
+ private ArrayMap<String, StatusBarIcon> mReplacingIcons;
+ private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIconsLegacy;
// Keep track of the last visible icon so collapsed container can report on its location
private IconState mLastVisibleIconState;
private IconState mFirstVisibleIconState;
@@ -167,6 +173,7 @@ public class NotificationIconContainer extends ViewGroup {
private final int[] mAbsolutePosition = new int[2];
private View mIsolatedIconForAnimation;
private int mThemedTextColorPrimary;
+ private Runnable mIsolatedIconAnimationEndRunnable;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -339,23 +346,29 @@ public class NotificationIconContainer extends ViewGroup {
}
private boolean isReplacingIcon(View child) {
- if (mReplacingIcons == null) {
- return false;
- }
if (!(child instanceof StatusBarIconView)) {
return false;
}
StatusBarIconView iconView = (StatusBarIconView) child;
Icon sourceIcon = iconView.getSourceIcon();
String groupKey = iconView.getNotification().getGroupKey();
- ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
- if (statusBarIcons != null) {
- StatusBarIcon replacedIcon = statusBarIcons.get(0);
- if (sourceIcon.sameAs(replacedIcon.icon)) {
- return true;
+ if (mIconContainerRefactorFlag.isEnabled()) {
+ if (mReplacingIcons == null) {
+ return false;
+ }
+ StatusBarIcon replacedIcon = mReplacingIcons.get(groupKey);
+ return replacedIcon != null && sourceIcon.sameAs(replacedIcon.icon);
+ } else {
+ if (mReplacingIconsLegacy == null) {
+ return false;
}
+ ArrayList<StatusBarIcon> statusBarIcons = mReplacingIconsLegacy.get(groupKey);
+ if (statusBarIcons != null) {
+ StatusBarIcon replacedIcon = statusBarIcons.get(0);
+ return sourceIcon.sameAs(replacedIcon.icon);
+ }
+ return false;
}
- return false;
}
@Override
@@ -681,14 +694,36 @@ public class NotificationIconContainer extends ViewGroup {
mAnimationsEnabled = enabled;
}
- public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+ public void setReplacingIconsLegacy(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+ mIconContainerRefactorFlag.assertInLegacyMode();
+ mReplacingIconsLegacy = replacingIcons;
+ }
+
+ public void setReplacingIcons(ArrayMap<String, StatusBarIcon> replacingIcons) {
+ if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
mReplacingIcons = replacingIcons;
}
+ @Deprecated
public void showIconIsolated(StatusBarIconView icon, boolean animated) {
+ mIconContainerRefactorFlag.assertInLegacyMode();
if (animated) {
- mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+ showIconIsolatedAnimated(icon, null);
+ } else {
+ showIconIsolated(icon);
}
+ }
+
+ public void showIconIsolatedAnimated(StatusBarIconView icon,
+ @Nullable Runnable onAnimationEnd) {
+ if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
+ mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon;
+ mIsolatedIconAnimationEndRunnable = onAnimationEnd;
+ showIconIsolated(icon);
+ }
+
+ public void showIconIsolated(StatusBarIconView icon) {
+ if (mIconContainerRefactorFlag.isUnexpectedlyInLegacyMode()) return;
mIsolatedIcon = icon;
updateState();
}
@@ -813,6 +848,11 @@ public class NotificationIconContainer extends ViewGroup {
animationProperties = UNISOLATION_PROPERTY;
animationProperties.setDelay(
mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0);
+ Consumer<Property> endAction = getEndAction();
+ if (endAction != null) {
+ animationProperties.setAnimationEndAction(endAction);
+ animationProperties.setAnimationCancelAction(endAction);
+ }
} else {
animationProperties = UNISOLATION_PROPERTY_OTHERS;
animationProperties.setDelay(
@@ -836,6 +876,18 @@ public class NotificationIconContainer extends ViewGroup {
needsCannedAnimation = false;
}
+ @Nullable
+ private Consumer<Property> getEndAction() {
+ if (mIsolatedIconAnimationEndRunnable == null) return null;
+ final Runnable endRunnable = mIsolatedIconAnimationEndRunnable;
+ return prop -> {
+ endRunnable.run();
+ if (mIsolatedIconAnimationEndRunnable == endRunnable) {
+ mIsolatedIconAnimationEndRunnable = null;
+ }
+ };
+ }
+
@Override
public void initFrom(View view) {
super.initFrom(view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 54d81b83197e..5a8b636e54fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -300,7 +300,8 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotCast, false);
// connected display
- mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display, null);
+ mIconController.setIcon(mSlotConnectedDisplay, R.drawable.stat_sys_connected_display,
+ mResources.getString(R.string.connected_display_icon_desc));
mIconController.setIconVisibility(mSlotConnectedDisplay, false);
// hotspot
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 5b552640f397..744d70e36972 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import static java.lang.Float.isNaN;
@@ -51,7 +54,6 @@ import com.android.settingslib.Utils;
import com.android.systemui.CoreStartable;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
import com.android.systemui.dagger.SysUISingleton;
@@ -62,7 +64,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -273,6 +277,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private CoroutineDispatcher mMainDispatcher;
private boolean mIsBouncerToGoneTransitionRunning = false;
private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
+ private AlternateBouncerToGoneTransitionViewModel mAlternateBouncerToGoneTransitionViewModel;
private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
(ScrimAlpha alphas) -> {
mInFrontAlpha = alphas.getFrontAlpha();
@@ -285,7 +290,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mScrimBehind.setViewAlpha(mBehindAlpha);
};
- Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
+ Consumer<TransitionStep> mBouncerToGoneTransition;
@Inject
public ScrimController(
@@ -304,6 +309,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+ AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
WallpaperRepository wallpaperRepository,
@Main CoroutineDispatcher mainDispatcher,
@@ -349,6 +355,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
});
mColors = new GradientColors();
mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
+ mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
mWallpaperRepository = wallpaperRepository;
mMainDispatcher = mainDispatcher;
@@ -405,7 +412,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
// Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure
// to report back that keyguard has faded away. This fixes cases where the scrim state was
// rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl
- mPrimaryBouncerToGoneTransition =
+ mBouncerToGoneTransition =
(TransitionStep step) -> {
TransitionState state = step.getTransitionState();
@@ -425,10 +432,17 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
};
- collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
- mPrimaryBouncerToGoneTransition, mMainDispatcher);
+ // PRIMARY_BOUNCER->GONE
+ collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE),
+ mBouncerToGoneTransition, mMainDispatcher);
collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
mScrimAlphaConsumer, mMainDispatcher);
+
+ // ALTERNATE_BOUNCER->GONE
+ collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE),
+ mBouncerToGoneTransition, mMainDispatcher);
+ collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(),
+ mScrimAlphaConsumer, mMainDispatcher);
}
// TODO(b/270984686) recompute scrim height accurately, based on shade contents.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 90fddd9ae22c..267b56378d82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -964,9 +964,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
}
- if (isShowing) {
- mMediaManager.updateMediaMetaData(false);
- }
mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
// setDozing(false) will call reset once we stop dozing. Also, if we're going away, there's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 57a8e6fe0d91..07e2571bcb38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -52,7 +52,7 @@ import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -80,7 +80,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
private final HeadsUpManager mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
- private final NotificationsInteractor mNotificationsInteractor;
+ private final NotificationAlertsInteractor mNotificationAlertsInteractor;
private final NotificationStackScrollLayoutController mNsslController;
private final LockscreenShadeTransitionController mShadeTransitionController;
private final PowerInteractor mPowerInteractor;
@@ -107,7 +107,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
- NotificationsInteractor notificationsInteractor,
+ NotificationAlertsInteractor notificationAlertsInteractor,
LockscreenShadeTransitionController shadeTransitionController,
PowerInteractor powerInteractor,
CommandQueue commandQueue,
@@ -127,7 +127,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
mQsController = quickSettingsController;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
- mNotificationsInteractor = notificationsInteractor;
+ mNotificationAlertsInteractor = notificationAlertsInteractor;
mNsslController = stackScrollerController;
mShadeTransitionController = shadeTransitionController;
mPowerInteractor = powerInteractor;
@@ -205,7 +205,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
// End old BaseStatusBar.userSwitched
mCommandQueue.animateCollapsePanels();
mMediaManager.clearCurrentMediaNotification();
- updateMediaMetaData(true, false);
}
@Override
@@ -220,11 +219,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
}
@Override
- public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
- mMediaManager.updateMediaMetaData(metaDataChanged);
- }
-
- @Override
public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
boolean nowExpanded) {
mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
@@ -309,7 +303,7 @@ class StatusBarNotificationPresenter implements NotificationPresenter, CommandQu
@Override
public boolean suppressInterruptions(NotificationEntry entry) {
- return !mNotificationsInteractor.areNotificationAlertsEnabled();
+ return !mNotificationAlertsInteractor.areNotificationAlertsEnabled();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 85fd2afed9ec..71e25e9556eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -17,42 +17,75 @@ package com.android.systemui.statusbar.phone
import android.app.Dialog
import android.content.Context
+import android.content.res.Configuration
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.Gravity
-import android.view.WindowManager
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowManager.LayoutParams.MATCH_PARENT
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
/** A dialog shown as a bottom sheet. */
open class SystemUIBottomSheetDialog(
context: Context,
- theme: Int = R.style.Theme_SystemUI_Dialog,
+ private val configurationController: ConfigurationController? = null,
+ theme: Int = R.style.Theme_SystemUI_Dialog
) : Dialog(context, theme) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ setupWindow()
+ setupEdgeToEdge()
+ setCanceledOnTouchOutside(true)
+ }
+ private fun setupWindow() {
window?.apply {
- setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
- addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-
+ setType(TYPE_STATUS_BAR_SUB_PANEL)
+ addPrivateFlags(SYSTEM_FLAG_SHOW_FOR_ALL_USERS or PRIVATE_FLAG_NO_MOVE_ANIMATION)
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
setGravity(Gravity.BOTTOM)
- val edgeToEdgeHorizontally =
- context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
- if (edgeToEdgeHorizontally) {
- decorView.setPadding(0, 0, 0, 0)
- setLayout(
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.WRAP_CONTENT
- )
-
- val lp = attributes
- lp.fitInsetsSides = 0
- lp.horizontalMargin = 0f
- attributes = lp
- }
+ decorView.setPadding(0, 0, 0, 0)
+ attributes =
+ attributes.apply {
+ fitInsetsSides = 0
+ horizontalMargin = 0f
+ }
}
- setCanceledOnTouchOutside(true)
}
+
+ private fun setupEdgeToEdge() {
+ val edgeToEdgeHorizontally =
+ context.resources.getBoolean(R.bool.config_edgeToEdgeBottomSheetDialog)
+ val width = if (edgeToEdgeHorizontally) MATCH_PARENT else WRAP_CONTENT
+ val height = WRAP_CONTENT
+ window?.setLayout(width, height)
+ }
+
+ override fun onStart() {
+ super.onStart()
+ configurationController?.addCallback(onConfigChanged)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ configurationController?.removeCallback(onConfigChanged)
+ }
+
+ /** Can be overridden by subclasses to receive config changed events. */
+ open fun onConfigurationChanged() {}
+
+ private val onConfigChanged =
+ object : ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ super.onConfigChanged(newConfig)
+ setupEdgeToEdge()
+ onConfigurationChanged()
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 9d627af357cb..2558645e3f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -45,6 +45,7 @@ import com.android.systemui.Dependency;
import com.android.systemui.res.R;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
@@ -54,8 +55,14 @@ import com.android.systemui.util.DialogKt;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
- * Base class for dialogs that should appear over panels and keyguard.
+ * Class for dialogs that should appear over panels and keyguard.
+ *
+ * DO NOT SUBCLASS THIS. See {@link SystemUIDialog.Delegate} for an interface that enables
+ * customizing behavior via composition instead of inheritance. Clients should implement the
+ * Delegate class and then pass their implementation into the SystemUIDialog constructor.
*
* Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to
* listeners on whether this dialog is showing.
@@ -72,6 +79,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
private final Context mContext;
private final FeatureFlags mFeatureFlags;
+ private final Delegate mDelegate;
@Nullable private final DismissReceiver mDismissReceiver;
private final Handler mHandler = new Handler();
private final SystemUIDialogManager mDialogManager;
@@ -101,18 +109,102 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
Dependency.get(SystemUIDialogManager.class),
Dependency.get(SysUiState.class),
Dependency.get(BroadcastDispatcher.class),
- Dependency.get(DialogLaunchAnimator.class));
+ Dependency.get(DialogLaunchAnimator.class),
+ new Delegate() {});
}
- public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
+ @Inject
+ public SystemUIDialog(
+ @Application Context context,
+ FeatureFlags featureFlags,
+ SystemUIDialogManager systemUIDialogManager,
+ SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator) {
+ this(context,
+ DEFAULT_THEME,
+ DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ featureFlags,
+ systemUIDialogManager,
+ sysUiState,
+ broadcastDispatcher,
+ dialogLaunchAnimator,
+ new Delegate(){});
+ }
+
+ public static class Factory {
+ private final Context mContext;
+ private final FeatureFlags mFeatureFlags;
+ private final SystemUIDialogManager mSystemUIDialogManager;
+ private final SysUiState mSysUiState;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final DialogLaunchAnimator mDialogLaunchAnimator;
+
+ @Inject
+ public Factory(
+ @Application Context context,
+ FeatureFlags featureFlags,
+ SystemUIDialogManager systemUIDialogManager,
+ SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator) {
+ mContext = context;
+ mFeatureFlags = featureFlags;
+ mSystemUIDialogManager = systemUIDialogManager;
+ mSysUiState = sysUiState;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
+ }
+
+ public SystemUIDialog create(Delegate delegate) {
+ return new SystemUIDialog(
+ mContext,
+ DEFAULT_THEME,
+ DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ mFeatureFlags,
+ mSystemUIDialogManager,
+ mSysUiState,
+ mBroadcastDispatcher,
+ mDialogLaunchAnimator,
+ delegate);
+ }
+ }
+
+ public SystemUIDialog(
+ Context context,
+ int theme,
+ boolean dismissOnDeviceLock,
FeatureFlags featureFlags,
SystemUIDialogManager dialogManager,
SysUiState sysUiState,
BroadcastDispatcher broadcastDispatcher,
DialogLaunchAnimator dialogLaunchAnimator) {
+ this(
+ context,
+ theme,
+ dismissOnDeviceLock,
+ featureFlags,
+ dialogManager,
+ sysUiState,
+ broadcastDispatcher,
+ dialogLaunchAnimator,
+ new Delegate(){});
+ }
+
+ public SystemUIDialog(
+ Context context,
+ int theme,
+ boolean dismissOnDeviceLock,
+ FeatureFlags featureFlags,
+ SystemUIDialogManager dialogManager,
+ SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator,
+ Delegate delegate) {
super(context, theme);
mContext = context;
mFeatureFlags = featureFlags;
+ mDelegate = delegate;
applyFlags(this);
WindowManager.LayoutParams attrs = getWindow().getAttributes();
@@ -127,7 +219,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
@Override
protected void onCreate(Bundle savedInstanceState) {
+ mDelegate.beforeCreate(this, savedInstanceState);
super.onCreate(savedInstanceState);
+ mDelegate.onCreate(this, savedInstanceState);
Configuration config = getContext().getResources().getConfiguration();
mLastConfigurationWidthDp = config.screenWidthDp;
@@ -172,6 +266,8 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
updateWindowSize();
}
+
+ mDelegate.onConfigurationChanged(this, configuration);
}
/**
@@ -212,7 +308,9 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()}
* should override this method instead.
*/
- protected void start() {}
+ protected void start() {
+ mDelegate.start(this);
+ }
@Override
protected final void onStop() {
@@ -234,7 +332,15 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()}
* should override this method instead.
*/
- protected void stop() {}
+ protected void stop() {
+ mDelegate.stop(this);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ mDelegate.onWindowFocusChanged(this, hasFocus);
+ }
public void setShowForAllUsers(boolean show) {
setShowForAllUsers(this, show);
@@ -353,7 +459,6 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
registerDismissListener(dialog, null);
}
-
/**
* Registers a listener that dismisses the given dialog when it receives
* the screen off / close system dialogs broadcast.
@@ -480,4 +585,42 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
}
}
+ /**
+ * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}.
+ *
+ * Implement this interface and then pass an instance of your implementation to
+ * {@link SystemUIDialog.Factory#create(Delegate)}.
+ */
+ public interface Delegate {
+ /**
+ * Called before {@link AlertDialog#onCreate} is called.
+ */
+ default void beforeCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+ /**
+ * Called after {@link AlertDialog#onCreate} is called.
+ */
+ default void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {}
+
+ /**
+ * Called after {@link AlertDialog#onStart} is called.
+ */
+ default void start(SystemUIDialog dialog) {}
+
+ /**
+ * Called after {@link AlertDialog#onStop} is called.
+ */
+ default void stop(SystemUIDialog dialog) {}
+
+ /**
+ * Called after {@link AlertDialog#onWindowFocusChanged(boolean)} is called.
+ */
+ default void onWindowFocusChanged(SystemUIDialog dialog, boolean hasFocus) {}
+
+ /**
+ * Called as part of
+ * {@link ViewRootImpl.ConfigChangedCallback#onConfigurationChanged(Configuration)}.
+ */
+ default void onConfigurationChanged(SystemUIDialog dialog, Configuration configuration) {}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
index 9b0c3fa7e92b..9645c69ab089 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/CommonRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/StatusBarPhoneDataLayerModule.kt
@@ -13,13 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.systemui.statusbar.phone.data
-package com.android.systemui.common.ui.data.repository
-
-import dagger.Binds
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepositoryModule
import dagger.Module
-@Module
-interface CommonRepositoryModule {
- @Binds fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
-}
+@Module(includes = [DarkIconRepositoryModule::class]) object StatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
index f6ed8c884816..ba377497bce4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationListRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/data/repository/DarkIconRepository.kt
@@ -13,23 +13,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.systemui.statusbar.notification.stack.data.repository
+package com.android.systemui.statusbar.phone.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-/** Repository for information about the current notification list. */
+/** Dark-mode state for tinting icons. */
+interface DarkIconRepository {
+ val darkState: StateFlow<DarkChange>
+}
+
@SysUISingleton
-class NotificationListRepository @Inject constructor() {
- private val _hasFilteredOutSeenNotifications = MutableStateFlow(false)
- val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
- _hasFilteredOutSeenNotifications.asStateFlow()
+class DarkIconRepositoryImpl
+@Inject
+constructor(
+ darkIconDispatcher: SysuiDarkIconDispatcher,
+) : DarkIconRepository {
+ override val darkState: StateFlow<DarkChange> = darkIconDispatcher.darkChangeFlow()
+}
- fun setHasFilteredOutSeenNotifications(value: Boolean) {
- _hasFilteredOutSeenNotifications.value = value
- }
+@Module
+interface DarkIconRepositoryModule {
+ @Binds fun bindImpl(impl: DarkIconRepositoryImpl): DarkIconRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
new file mode 100644
index 000000000000..246645ee0ead
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/domain/interactor/DarkIconInteractor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone.domain.interactor
+
+import android.graphics.Rect
+import com.android.systemui.statusbar.phone.data.repository.DarkIconRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** States pertaining to calculating colors for icons in dark mode. */
+class DarkIconInteractor @Inject constructor(repository: DarkIconRepository) {
+ /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.areas */
+ val tintAreas: Flow<Collection<Rect>> = repository.darkState.map { it.areas }
+ /**
+ * @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.darkIntensity
+ */
+ val darkIntensity: Flow<Float> = repository.darkState.map { it.darkIntensity }
+ /** @see com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange.tint */
+ val tintColor: Flow<Int> = repository.darkState.map { it.tint }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 63c022c5bb6b..e2a4714e6116 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -38,9 +38,13 @@ import com.android.app.animation.Interpolators;
import com.android.app.animation.InterpolatorsAndroidX;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
+import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -52,8 +56,15 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerViewModel;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
@@ -66,6 +77,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -83,6 +95,7 @@ import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
+
import kotlin.Unit;
/**
@@ -128,7 +141,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final OngoingCallController mOngoingCallController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
private final StatusBarLocationPublisher mLocationPublisher;
- private final FeatureFlags mFeatureFlags;
+ private final FeatureFlagsClassic mFeatureFlags;
private final NotificationIconAreaController mNotificationIconAreaController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final StatusBarIconController mStatusBarIconController;
@@ -142,6 +155,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final DumpManager mDumpManager;
private final StatusBarWindowStateController mStatusBarWindowStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final NotificationIconContainerViewModel mStatusBarIconsViewModel;
+ private final ConfigurationState mConfigurationState;
+ private final ConfigurationController mConfigurationController;
+ private final DozeParameters mDozeParameters;
+ private final ScreenOffAnimationController mScreenOffAnimationController;
+ private final NotificationIconContainerViewBinder.IconViewStore mStatusBarIconViewStore;
+ private final DemoModeController mDemoModeController;
private List<String> mBlockedIcons = new ArrayList<>();
private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
@@ -211,9 +231,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
StatusBarLocationPublisher locationPublisher,
NotificationIconAreaController notificationIconAreaController,
ShadeExpansionStateManager shadeExpansionStateManager,
- FeatureFlags featureFlags,
+ FeatureFlagsClassic featureFlags,
StatusBarIconController statusBarIconController,
- StatusBarIconController.DarkIconManager.Factory darkIconManagerFactory,
+ DarkIconManager.Factory darkIconManagerFactory,
CollapsedStatusBarViewModel collapsedStatusBarViewModel,
CollapsedStatusBarViewBinder collapsedStatusBarViewBinder,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
@@ -228,8 +248,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
@Main Executor mainExecutor,
DumpManager dumpManager,
StatusBarWindowStateController statusBarWindowStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor
- ) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ NotificationIconContainerStatusBarViewModel statusBarIconsViewModel,
+ ConfigurationState configurationState,
+ ConfigurationController configurationController,
+ DozeParameters dozeParameters,
+ ScreenOffAnimationController screenOffAnimationController,
+ StatusBarNotificationIconViewStore statusBarIconViewStore,
+ DemoModeController demoModeController) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
mAnimationScheduler = animationScheduler;
@@ -254,18 +280,55 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mDumpManager = dumpManager;
mStatusBarWindowStateController = statusBarWindowStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mStatusBarIconsViewModel = statusBarIconsViewModel;
+ mConfigurationState = configurationState;
+ mConfigurationController = configurationController;
+ mDozeParameters = dozeParameters;
+ mScreenOffAnimationController = screenOffAnimationController;
+ mStatusBarIconViewStore = statusBarIconViewStore;
+ mDemoModeController = demoModeController;
}
+ private final DemoMode mDemoModeCallback = new DemoMode() {
+ @Override
+ public List<String> demoCommands() {
+ return List.of(DemoMode.COMMAND_NOTIFICATIONS);
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (mNotificationIconAreaInner == null) return;
+ String visible = args.getString("visible");
+ if ("false".equals(visible)) {
+ mNotificationIconAreaInner.setVisibility(View.INVISIBLE);
+ } else {
+ mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onDemoModeFinished() {
+ if (mNotificationIconAreaInner == null) return;
+ mNotificationIconAreaInner.setVisibility(View.VISIBLE);
+ }
+ };
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mStatusBarWindowStateController.addListener(mStatusBarWindowStateListener);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mDemoModeController.addCallback(mDemoModeCallback);
+ }
}
@Override
public void onDestroy() {
super.onDestroy();
mStatusBarWindowStateController.removeListener(mStatusBarWindowStateListener);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mDemoModeController.removeCallback(mDemoModeCallback);
+ }
}
@Override
@@ -405,14 +468,31 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
/** Initializes views related to the notification icon area. */
public void initNotificationIconArea() {
- ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
- mNotificationIconAreaInner =
- mNotificationIconAreaController.getNotificationInnerAreaView();
- if (mNotificationIconAreaInner.getParent() != null) {
- ((ViewGroup) mNotificationIconAreaInner.getParent())
- .removeView(mNotificationIconAreaInner);
- }
- notificationIconArea.addView(mNotificationIconAreaInner);
+ ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
+ if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR)) {
+ mNotificationIconAreaInner =
+ LayoutInflater.from(getContext())
+ .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+ NotificationIconContainer notificationIcons =
+ notificationIconArea.requireViewById(R.id.notificationIcons);
+ NotificationIconContainerViewBinder.bind(
+ notificationIcons,
+ mStatusBarIconsViewModel,
+ mConfigurationState,
+ mConfigurationController,
+ mDozeParameters,
+ mFeatureFlags,
+ mScreenOffAnimationController,
+ mStatusBarIconViewStore);
+ } else {
+ mNotificationIconAreaInner =
+ mNotificationIconAreaController.getNotificationInnerAreaView();
+ if (mNotificationIconAreaInner.getParent() != null) {
+ ((ViewGroup) mNotificationIconAreaInner.getParent())
+ .removeView(mNotificationIconAreaInner);
+ }
+ notificationIconArea.addView(mNotificationIconAreaInner);
+ }
updateNotificationIconAreaAndCallChip(/* animate= */ false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
index 21acfb41f10c..0a2bbe580b99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationControllerExt.kt
@@ -13,7 +13,8 @@
*/
package com.android.systemui.statusbar.policy
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import android.content.res.Configuration
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -23,14 +24,47 @@ import kotlinx.coroutines.flow.Flow
* @see ConfigurationController.ConfigurationListener.onDensityOrFontScaleChanged
*/
val ConfigurationController.onDensityOrFontScaleChanged: Flow<Unit>
- get() =
- ConflatedCallbackFlow.conflatedCallbackFlow {
- val listener =
- object : ConfigurationController.ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- trySend(Unit)
- }
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onDensityOrFontScaleChanged() {
+ trySend(Unit)
}
- addCallback(listener)
- awaitClose { removeCallback(listener) }
- }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
+
+/**
+ * A [Flow] that emits whenever the theme has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onThemeChanged
+ */
+val ConfigurationController.onThemeChanged: Flow<Unit>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ trySend(Unit)
+ }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
+
+/**
+ * A [Flow] that emits whenever the configuration has changed.
+ *
+ * @see ConfigurationController.ConfigurationListener.onConfigChanged
+ */
+val ConfigurationController.onConfigChanged: Flow<Configuration>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySend(newConfig)
+ }
+ }
+ addCallback(listener)
+ awaitClose { removeCallback(listener) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
index 9269df31e37e..8c66c2f0fab3 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLatencyTracker.kt
@@ -19,6 +19,7 @@ package com.android.systemui.unfold
import android.content.ContentResolver
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
+import android.os.Trace
import android.util.Log
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.SysUISingleton
@@ -57,6 +58,7 @@ constructor(
private var folded: Boolean? = null
private var isTransitionEnabled: Boolean? = null
private val foldStateListener = FoldStateListener(context)
+ private var unfoldInProgress = false
private val isFoldable: Boolean
get() =
context.resources
@@ -95,7 +97,7 @@ constructor(
// the unfold animation (e.g. it could be disabled because of battery saver).
// When animation is enabled finishing of the tracking will be done in onTransitionStarted.
if (folded == false && isTransitionEnabled == false) {
- latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+ onUnfoldEnded()
if (DEBUG) {
Log.d(TAG, "onScreenTurnedOn: ending ACTION_SWITCH_DISPLAY_UNFOLD")
@@ -116,7 +118,7 @@ constructor(
}
if (folded == false && isTransitionEnabled == true) {
- latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+ onUnfoldEnded()
if (DEBUG) {
Log.d(TAG, "onTransitionStarted: ending ACTION_SWITCH_DISPLAY_UNFOLD")
@@ -124,6 +126,22 @@ constructor(
}
}
+ private fun onUnfoldStarted() {
+ if (unfoldInProgress) return
+ unfoldInProgress = true
+ // As LatencyTracker might be disabled, let's also log a parallel slice to the trace to be
+ // able to debug all cases.
+ latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_APP, UNFOLD_IN_PROGRESS_TRACE_NAME, /* cookie= */ 0)
+ }
+
+ private fun onUnfoldEnded() {
+ if (!unfoldInProgress) return
+ unfoldInProgress = false
+ latencyTracker.onActionEnd(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+ Trace.endAsyncSection(UNFOLD_IN_PROGRESS_TRACE_NAME, 0)
+ }
+
private fun onFoldEvent(folded: Boolean) {
val oldFolded = this.folded
@@ -139,7 +157,7 @@ constructor(
// unfolding the device.
if (oldFolded != null && !folded) {
// Unfolding started
- latencyTracker.onActionStart(LatencyTracker.ACTION_SWITCH_DISPLAY_UNFOLD)
+ onUnfoldStarted()
isTransitionEnabled =
transitionProgressProvider.isPresent && contentResolver.areAnimationsEnabled()
@@ -159,4 +177,5 @@ constructor(
}
private const val TAG = "UnfoldLatencyTracker"
+private const val UNFOLD_IN_PROGRESS_TRACE_NAME = "Switch displays during unfold"
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
new file mode 100644
index 000000000000..ed960f31228a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.unfold
+
+import android.content.Context
+import android.os.Trace
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.unfold.system.DeviceStateRepository
+import com.android.systemui.unfold.updates.FoldStateRepository
+import com.android.systemui.util.TraceStateLogger
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Logs several unfold related details in a trace. Mainly used for debugging and investigate
+ * droidfooders traces.
+ */
+@SysUISingleton
+class UnfoldTraceLogger
+@Inject
+constructor(
+ private val context: Context,
+ private val foldStateRepository: FoldStateRepository,
+ @Application private val applicationScope: CoroutineScope,
+ private val deviceStateRepository: DeviceStateRepository
+) : CoreStartable {
+ private val isFoldable: Boolean
+ get() =
+ context.resources
+ .getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
+ .isNotEmpty()
+
+ override fun start() {
+ if (!isFoldable) return
+
+ applicationScope.launch {
+ val foldUpdateLogger = TraceStateLogger("FoldUpdate")
+ foldStateRepository.foldUpdate.collect { foldUpdateLogger.log(it.name) }
+ }
+
+ applicationScope.launch {
+ foldStateRepository.hingeAngle.collect {
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt())
+ }
+ }
+ applicationScope.launch {
+ val foldedStateLogger = TraceStateLogger("FoldedState")
+ deviceStateRepository.isFolded.collect { isFolded ->
+ foldedStateLogger.log(
+ if (isFolded) {
+ "folded"
+ } else {
+ "unfolded"
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index ed3eacd27c0a..71314f1f1775 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -19,6 +19,7 @@ package com.android.systemui.unfold
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.os.SystemProperties
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
@@ -34,16 +35,26 @@ import com.android.systemui.unfold.util.UnfoldOnlyProgressProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
import com.android.systemui.util.time.SystemClockImpl
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider
+import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Named
import javax.inject.Provider
import javax.inject.Singleton
-@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class])
+@Module(
+ includes =
+ [
+ UnfoldSharedModule::class,
+ SystemUnfoldSharedModule::class,
+ UnfoldTransitionModule.Bindings::class
+ ]
+)
class UnfoldTransitionModule {
@Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
@@ -136,13 +147,22 @@ class UnfoldTransitionModule {
null
}
- return resultingProvider?.get()?.orElse(null)?.let {
- unfoldProgressProvider -> UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
- } ?: ShellUnfoldProgressProvider.NO_PROVIDER
+ return resultingProvider?.get()?.orElse(null)?.let { unfoldProgressProvider ->
+ UnfoldProgressProvider(unfoldProgressProvider, foldProvider)
+ }
+ ?: ShellUnfoldProgressProvider.NO_PROVIDER
}
@Provides
fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
+
+ @Module
+ interface Bindings {
+ @Binds
+ @IntoMap
+ @ClassKey(UnfoldTraceLogger::class)
+ fun bindUnfoldTraceLogger(impl: UnfoldTraceLogger): CoreStartable
+ }
}
const val UNFOLD_STATUS_BAR = "unfold_status_bar"
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
index 0a44bda70238..f0c7be63b64c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -4,27 +4,74 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.Tracing
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.TraceUtils.Companion.coroutineTracingIsEnabled
+import com.android.systemui.util.tracing.TraceContextElement
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import javax.inject.Qualifier
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/** Key associated with a [Boolean] flag that enables or disables the coroutine tracing feature. */
+@Qualifier
+annotation class CoroutineTracingEnabledKey
+
+/**
+ * Same as [@Application], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedApplication
+
+/**
+ * Same as [@Background], but does not make use of flags. This should only be used when early usage
+ * of [@Application] would introduce a circular dependency on [FeatureFlagsClassic].
+ */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnflaggedBackground
/** Providers for various coroutines-related constructs. */
@Module
-object CoroutinesModule {
+class CoroutinesModule {
@Provides
@SysUISingleton
@Application
fun applicationScope(
- @Main dispatcher: CoroutineDispatcher,
- ): CoroutineScope = CoroutineScope(dispatcher)
+ @Main dispatcherContext: CoroutineContext,
+ ): CoroutineScope = CoroutineScope(dispatcherContext)
+
+ @Provides
+ @SysUISingleton
+ @UnflaggedApplication
+ fun unflaggedApplicationScope(): CoroutineScope = CoroutineScope(Dispatchers.Main.immediate)
@Provides
@SysUISingleton
@Main
+ @Deprecated(
+ "Use @Main CoroutineContext instead",
+ ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+ )
fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
+ @Provides
+ @SysUISingleton
+ @Main
+ fun mainCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+ return Dispatchers.Main.immediate + tracingCoroutineContext
+ }
+
/**
* Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where
* X is the number of CPU cores available.
@@ -37,5 +84,42 @@ object CoroutinesModule {
@Provides
@SysUISingleton
@Background
+ @Deprecated(
+ "Use @Background CoroutineContext instead",
+ ReplaceWith("bgCoroutineContext()", "kotlin.coroutines.CoroutineContext")
+ )
fun bgDispatcher(): CoroutineDispatcher = Dispatchers.IO
+
+
+ @Provides
+ @Background
+ @SysUISingleton
+ fun bgCoroutineContext(@Tracing tracingCoroutineContext: CoroutineContext): CoroutineContext {
+ return Dispatchers.IO + tracingCoroutineContext
+ }
+
+ @Provides
+ @UnflaggedBackground
+ @SysUISingleton
+ fun unflaggedBackgroundCoroutineContext(): CoroutineContext {
+ return Dispatchers.IO
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Provides
+ @Tracing
+ @SysUISingleton
+ fun tracingCoroutineContext(
+ @CoroutineTracingEnabledKey enableTracing: Boolean
+ ): CoroutineContext = if (enableTracing) TraceContextElement() else EmptyCoroutineContext
+
+ companion object {
+ @[Provides CoroutineTracingEnabledKey]
+ fun provideIsCoroutineTracingEnabledKey(featureFlags: FeatureFlagsClassic): Boolean {
+ return if (featureFlags.isEnabled(Flags.COROUTINE_TRACING)) {
+ coroutineTracingIsEnabled = true
+ true
+ } else false
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
new file mode 100644
index 000000000000..909a18be4b9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/DisposableHandleExt.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.awaitCancellation
+
+/**
+ * Suspends to keep getting updates until cancellation. Once cancelled, mark this as eligible for
+ * garbage collection.
+ *
+ * This utility is useful if you want to bind a [repeatWhenAttached] invocation to the lifetime of a
+ * coroutine, such that cancelling the coroutine cleans up the handle. For example:
+ * ```
+ * myFlow.collectLatest { value ->
+ * val disposableHandle = myView.repeatWhenAttached { doStuff() }
+ * doSomethingWith(value)
+ * // un-bind when done
+ * disposableHandle.awaitCancellationThenDispose()
+ * }
+ * ```
+ */
+suspend fun DisposableHandle.awaitCancellationThenDispose() {
+ try {
+ awaitCancellation()
+ } finally {
+ dispose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
index 83ff78980880..8fe57e116405 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt
@@ -61,10 +61,10 @@ fun <T, R> Flow<T>.pairwiseBy(transform: suspend (old: T, new: T) -> R): Flow<R>
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T, R> Flow<T>.pairwiseBy(
- initialValue: T,
- transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(initialValue) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+ initialValue: S,
+ transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = pairwiseBy(getInitialValue = { initialValue }, transform)
/**
* Returns a new [Flow] that combines the two most recent emissions from [this] using [transform].
@@ -75,10 +75,16 @@ fun <T, R> Flow<T>.pairwiseBy(
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T, R> Flow<T>.pairwiseBy(
- getInitialValue: suspend () -> T,
- transform: suspend (previousValue: T, newValue: T) -> R,
-): Flow<R> = onStart { emit(getInitialValue()) }.pairwiseBy(transform)
+fun <S, T : S, R> Flow<T>.pairwiseBy(
+ getInitialValue: suspend () -> S,
+ transform: suspend (previousValue: S, newValue: T) -> R,
+): Flow<R> = flow {
+ var previousValue: S = getInitialValue()
+ collect { newVal ->
+ emit(transform(previousValue, newVal))
+ previousValue = newVal
+ }
+}
/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. Note that the new
@@ -86,7 +92,7 @@ fun <T, R> Flow<T>.pairwiseBy(
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
+fun <T> Flow<T>.pairwise(): Flow<WithPrev<T, T>> = pairwiseBy(::WithPrev)
/**
* Returns a new [Flow] that produces the two most recent emissions from [this]. [initialValue] will
@@ -94,10 +100,11 @@ fun <T> Flow<T>.pairwise(): Flow<WithPrev<T>> = pairwiseBy(::WithPrev)
*
* Useful for code that needs to compare the current value to the previous value.
*/
-fun <T> Flow<T>.pairwise(initialValue: T): Flow<WithPrev<T>> = pairwiseBy(initialValue, ::WithPrev)
+fun <S, T : S> Flow<T>.pairwise(initialValue: S): Flow<WithPrev<S, T>> =
+ pairwiseBy(initialValue, ::WithPrev)
/** Holds a [newValue] emitted from a [Flow], along with the [previousValue] emitted value. */
-data class WithPrev<T>(val previousValue: T, val newValue: T)
+data class WithPrev<out S, out T : S>(val previousValue: S, val newValue: T)
/**
* Returns a new [Flow] that combines the [Set] changes between each emission from [this] using
@@ -265,7 +272,127 @@ fun <T> Flow<T>.throttle(periodMs: Long, clock: SystemClock): Flow<T> = channelF
* immediately invoke [getValue] to establish its initial value.
*/
inline fun <T> CoroutineScope.stateFlow(
- changedSignals: Flow<Unit>,
+ changedSignals: Flow<*>,
crossinline getValue: () -> T,
): StateFlow<T> =
changedSignals.map { getValue() }.stateIn(this, SharingStarted.Eagerly, getValue())
+
+inline fun <T1, T2, T3, T4, T5, T6, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*>
+ ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6, flow7, flow8) {
+ args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[7] as T8
+ )
+ }
+}
+
+inline fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ flow6: Flow<T6>,
+ flow7: Flow<T7>,
+ flow8: Flow<T8>,
+ flow9: Flow<T9>,
+ crossinline transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9) -> R
+): Flow<R> {
+ return kotlinx.coroutines.flow.combine(
+ flow,
+ flow2,
+ flow3,
+ flow4,
+ flow5,
+ flow6,
+ flow7,
+ flow8,
+ flow9
+ ) { args: Array<*> ->
+ @Suppress("UNCHECKED_CAST")
+ transform(
+ args[0] as T1,
+ args[1] as T2,
+ args[2] as T3,
+ args[3] as T4,
+ args[4] as T5,
+ args[5] as T6,
+ args[6] as T7,
+ args[6] as T8,
+ args[6] as T9,
+ )
+ }
+}
+
+/**
+ * Returns a [Flow] that immediately emits [Unit] when started, then emits from the given upstream
+ * [Flow] as normal.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun Flow<Unit>.emitOnStart(): Flow<Unit> = onStart { emit(Unit) }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
new file mode 100644
index 000000000000..41cd95b780c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 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
+
+/** Like [mapValues], but discards `null` values returned from [block]. */
+fun <K, V, R> Map<K, V>.mapValuesNotNull(block: (Map.Entry<K, V>) -> R?): Map<K, R> = buildMap {
+ this@mapValuesNotNull.mapValuesNotNullTo(this, block)
+}
+
+/** Like [mapValuesTo], but discards `null` values returned from [block]. */
+fun <K, V, R, M : MutableMap<in K, in R>> Map<out K, V>.mapValuesNotNullTo(
+ destination: M,
+ block: (Map.Entry<K, V>) -> R?,
+): M {
+ for (entry in this) {
+ block(entry)?.also { destination.put(entry.key, it) }
+ }
+ return destination
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
index 51d2afabd7f9..1112d6f4f25c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ui/AnimatedValue.kt
@@ -17,26 +17,58 @@
package com.android.systemui.util.ui
+import com.android.systemui.util.ui.AnimatedValue.Animating
+import com.android.systemui.util.ui.AnimatedValue.NotAnimating
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transformLatest
/**
* A state comprised of a [value] of type [T] paired with a boolean indicating whether or not the
* [value] [isAnimating] in the UI.
*/
-data class AnimatedValue<T>(
- val value: T,
- val isAnimating: Boolean,
-)
+sealed interface AnimatedValue<out T> {
+
+ /** A [state][value] that is not actively animating in the UI. */
+ data class NotAnimating<out T>(val value: T) : AnimatedValue<T>
+
+ /**
+ * A [state][value] that is actively animating in the UI. Invoking [onStopAnimating] will signal
+ * the source of the state to stop animating.
+ */
+ data class Animating<out T>(
+ val value: T,
+ val onStopAnimating: () -> Unit,
+ ) : AnimatedValue<T>
+}
+
+/** The state held in this [AnimatedValue]. */
+inline val <T> AnimatedValue<T>.value: T
+ get() =
+ when (this) {
+ is Animating -> value
+ is NotAnimating -> value
+ }
+
+/** Returns whether or not this [AnimatedValue] is animating or not. */
+inline val <T> AnimatedValue<T>.isAnimating: Boolean
+ get() = this is Animating<T>
+
+/**
+ * If this [AnimatedValue] [isAnimating], then signal that the animation should be stopped.
+ * Otherwise, do nothing.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun AnimatedValue<*>.stopAnimating() {
+ if (this is Animating) onStopAnimating()
+}
/**
* An event comprised of a [value] of type [T] paired with a [boolean][startAnimating] indicating
* whether or not this event should start an animation.
*/
-data class AnimatableEvent<T>(
+data class AnimatableEvent<out T>(
val value: T,
val startAnimating: Boolean,
)
@@ -47,16 +79,87 @@ data class AnimatableEvent<T>(
* [AnimatableEvent.startAnimating] value is `true`. When [completionEvents] emits a value, the
* [AnimatedValue.isAnimating] will flip to `false`.
*/
-fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(
- completionEvents: Flow<Any?>,
-): Flow<AnimatedValue<T>> = transformLatest { (value, startAnimating) ->
- emit(AnimatedValue(value, isAnimating = startAnimating))
- if (startAnimating) {
- // Wait for a completion now that we've started animating
- completionEvents
- .map { Unit } // replace the event so that it's never `null`
- .firstOrNull() // `null` indicates an empty flow
- // emit the new state if the flow was not empty.
- ?.run { emit(AnimatedValue(value, isAnimating = false)) }
+fun <T> Flow<AnimatableEvent<T>>.toAnimatedValueFlow(): Flow<AnimatedValue<T>> =
+ transformLatest { (value, startAnimating) ->
+ if (startAnimating) {
+ val onCompleted = CompletableDeferred<Unit>()
+ emit(Animating(value) { onCompleted.complete(Unit) })
+ // Wait for a completion now that we've started animating
+ onCompleted.await()
+ }
+ emit(NotAnimating(value))
+ }
+
+/**
+ * Zip two [AnimatedValue]s together into a single [AnimatedValue], using [block] to combine the
+ * [value]s of each.
+ *
+ * If either [AnimatedValue] [isAnimating], then the result is also animating. Invoking
+ * [stopAnimating] on the result is equivalent to invoking [stopAnimating] on each input.
+ */
+inline fun <A, B, Z> zip(
+ valueA: AnimatedValue<A>,
+ valueB: AnimatedValue<B>,
+ block: (A, B) -> Z,
+): AnimatedValue<Z> {
+ val zippedValue = block(valueA.value, valueB.value)
+ return when (valueA) {
+ is Animating ->
+ when (valueB) {
+ is Animating ->
+ Animating(zippedValue) {
+ valueA.onStopAnimating()
+ valueB.onStopAnimating()
+ }
+ is NotAnimating -> Animating(zippedValue, valueA.onStopAnimating)
+ }
+ is NotAnimating ->
+ when (valueB) {
+ is Animating -> Animating(zippedValue, valueB.onStopAnimating)
+ is NotAnimating -> NotAnimating(zippedValue)
+ }
}
}
+
+/**
+ * Flattens a nested [AnimatedValue], the result of which holds the [value] of the inner
+ * [AnimatedValue].
+ *
+ * If either the outer or inner [AnimatedValue] [isAnimating], then the flattened result is also
+ * animating. Invoking [stopAnimating] on the result is equivalent to invoking [stopAnimating] on
+ * both the outer and inner values.
+ */
+@Suppress("NOTHING_TO_INLINE")
+inline fun <T> AnimatedValue<AnimatedValue<T>>.flatten(): AnimatedValue<T> = flatMap { it }
+
+/**
+ * Returns an [AnimatedValue], the [value] of which is the result of the given [value] applied to
+ * [block].
+ */
+inline fun <A, B> AnimatedValue<A>.map(block: (A) -> B): AnimatedValue<B> =
+ when (this) {
+ is Animating -> Animating(block(value), ::stopAnimating)
+ is NotAnimating -> NotAnimating(block(value))
+ }
+
+/**
+ * Returns an [AnimatedValue] from the result of [block] being invoked on the [value] original
+ * [AnimatedValue].
+ *
+ * If either the input [AnimatedValue] or the result of [block] [isAnimating], then the flattened
+ * result is also animating. Invoking [stopAnimating] on the result is equivalent to invoking
+ * [stopAnimating] on both values.
+ */
+inline fun <A, B> AnimatedValue<A>.flatMap(block: (A) -> AnimatedValue<B>): AnimatedValue<B> =
+ when (this) {
+ is NotAnimating -> block(value)
+ is Animating ->
+ when (val inner = block(value)) {
+ is Animating ->
+ Animating(inner.value) {
+ onStopAnimating()
+ inner.onStopAnimating()
+ }
+ is NotAnimating -> Animating(inner.value, onStopAnimating)
+ }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt b/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt
new file mode 100644
index 000000000000..6d45d23879e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/view/LayoutInflaterExt.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.view
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
+import com.android.systemui.util.kotlin.stateFlow
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+
+/**
+ * Perform an inflation right away, then re-inflate whenever the [flow] emits, and call [onInflate]
+ * on the resulting view each time. Dispose of the [DisposableHandle] returned by [onInflate] when
+ * done.
+ *
+ * This never completes unless cancelled, it just suspends and waits for updates.
+ *
+ * For parameters [resource], [root] and [attachToRoot], see [LayoutInflater.inflate].
+ *
+ * An example use-case of this is when a view needs to be re-inflated whenever a configuration
+ * change occurs, which would require the ViewBinder to then re-bind the new view. For example, the
+ * code in the parent view's binder would look like:
+ * ```
+ * parentView.repeatWhenAttached {
+ * LayoutInflater.from(parentView.context)
+ * .reinflateOnChange(
+ * R.layout.my_layout,
+ * parentView,
+ * attachToRoot = false,
+ * coroutineScope = lifecycleScope,
+ * configurationController.onThemeChanged,
+ * ),
+ * ) { view ->
+ * ChildViewBinder.bind(view as ChildView, childViewModel)
+ * }
+ * }
+ * ```
+ *
+ * In turn, the bind method (passed through [onInflate]) uses [repeatWhenAttached], which returns a
+ * [DisposableHandle].
+ */
+suspend fun LayoutInflater.reinflateAndBindLatest(
+ resource: Int,
+ root: ViewGroup?,
+ attachToRoot: Boolean,
+ flow: Flow<Unit>,
+ onInflate: (View) -> DisposableHandle?,
+) = coroutineScope {
+ val viewFlow: Flow<View> = stateFlow(flow) { inflate(resource, root, attachToRoot) }
+ viewFlow.bindLatest(onInflate)
+}
+
+/**
+ * Use the [bind] method to bind the view every time this flow emits, and suspend to await for more
+ * updates. New emissions lead to the previous binding call being cancelled if not completed.
+ * Dispose of the [DisposableHandle] returned by [bind] when done.
+ */
+suspend fun Flow<View>.bindLatest(bind: (View) -> DisposableHandle?) {
+ this.collectLatest { view ->
+ val disposableHandle = bind(view)
+ disposableHandle?.awaitCancellationThenDispose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 9dca013f8aa4..fdf5966419b4 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -109,6 +109,7 @@ public class ImageWallpaper extends WallpaperService {
private WallpaperManager mWallpaperManager;
private final WallpaperLocalColorExtractor mWallpaperLocalColorExtractor;
private SurfaceHolder mSurfaceHolder;
+ private boolean mDrawn = false;
@VisibleForTesting
static final int MIN_SURFACE_WIDTH = 128;
@VisibleForTesting
@@ -125,8 +126,6 @@ public class ImageWallpaper extends WallpaperService {
private int mBitmapUsages = 0;
private final Object mLock = new Object();
- private boolean mIsLockscreenLiveWallpaperEnabled;
-
CanvasEngine() {
super();
setFixedSizeAllowed(true);
@@ -170,12 +169,8 @@ public class ImageWallpaper extends WallpaperService {
Log.d(TAG, "onCreate");
}
mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
- mIsLockscreenLiveWallpaperEnabled = mWallpaperManager
- .isLockscreenLiveWallpaperEnabled();
mSurfaceHolder = surfaceHolder;
- Rect dimensions = mIsLockscreenLiveWallpaperEnabled
- ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true)
- : mWallpaperManager.peekBitmapDimensions();
+ Rect dimensions = mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true);
int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
mSurfaceHolder.setFixedSize(width, height);
@@ -239,6 +234,7 @@ public class ImageWallpaper extends WallpaperService {
private void drawFrameSynchronized() {
synchronized (mLock) {
+ if (mDrawn) return;
drawFrameInternal();
}
}
@@ -276,6 +272,7 @@ public class ImageWallpaper extends WallpaperService {
Rect dest = mSurfaceHolder.getSurfaceFrame();
try {
canvas.drawBitmap(bitmap, null, dest, null);
+ mDrawn = true;
} finally {
surface.unlockCanvasAndPost(canvas);
}
@@ -324,10 +321,8 @@ public class ImageWallpaper extends WallpaperService {
boolean loadSuccess = false;
Bitmap bitmap;
try {
- bitmap = mIsLockscreenLiveWallpaperEnabled
- ? mWallpaperManager.getBitmapAsUser(
- mUserTracker.getUserId(), false, getSourceFlag(), true)
- : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
+ bitmap = mWallpaperManager.getBitmapAsUser(
+ mUserTracker.getUserId(), false, getSourceFlag(), true);
if (bitmap != null
&& bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
throw new RuntimeException("Wallpaper is too large to draw!");
@@ -338,18 +333,11 @@ public class ImageWallpaper extends WallpaperService {
// be loaded, we will go into a cycle. Don't do a build where the
// default wallpaper can't be loaded.
Log.w(TAG, "Unable to load wallpaper!", exception);
- if (mIsLockscreenLiveWallpaperEnabled) {
- mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
- } else {
- mWallpaperManager.clearWallpaper(
- WallpaperManager.FLAG_SYSTEM, mUserTracker.getUserId());
- }
+ mWallpaperManager.clearWallpaper(getWallpaperFlags(), mUserTracker.getUserId());
try {
- bitmap = mIsLockscreenLiveWallpaperEnabled
- ? mWallpaperManager.getBitmapAsUser(
- mUserTracker.getUserId(), false, getSourceFlag(), true)
- : mWallpaperManager.getBitmapAsUser(mUserTracker.getUserId(), false);
+ bitmap = mWallpaperManager.getBitmapAsUser(
+ mUserTracker.getUserId(), false, getSourceFlag(), true);
} catch (RuntimeException | OutOfMemoryError e) {
Log.w(TAG, "Unable to load default wallpaper!", e);
bitmap = null;
@@ -370,9 +358,7 @@ public class ImageWallpaper extends WallpaperService {
mBitmap.recycle();
}
mBitmap = bitmap;
- mWideColorGamut = mIsLockscreenLiveWallpaperEnabled
- ? mWallpaperManager.wallpaperSupportsWcg(getSourceFlag())
- : mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
+ mWideColorGamut = mWallpaperManager.wallpaperSupportsWcg(getSourceFlag());
// +2 usages for the color extraction and the delayed unload.
mBitmapUsages += 2;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 897c4da0fae2..1e801aeb5a29 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -33,6 +33,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
+import android.util.Log;
import android.view.Display;
import android.view.KeyEvent;
@@ -66,6 +67,7 @@ import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -371,6 +373,13 @@ public final class WMShell implements
@Override
public void dump(PrintWriter pw, String[] args) {
+ Log.d(TAG, "Dumping with args: " + String.join(", ", args));
+
+ // Strip out the SysUI "dependency" arg before sending to WMShell
+ if (args[0].equals("dependency")) {
+ args = Arrays.copyOfRange(args, 1, args.length);
+ }
+
// Handle commands if provided
if (mShell.handleCommand(args, pw)) {
return;
diff --git a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
index ea74510a9070..c4b43e1cbe77 100644
--- a/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
+++ b/packages/SystemUI/tests/src/com/android/SysUITestModule.kt
@@ -16,10 +16,17 @@
package com.android
import android.content.Context
+import android.content.res.Resources
+import android.testing.TestableContext
+import android.testing.TestableResources
import com.android.systemui.FakeSystemUiModule
import com.android.systemui.SysuiTestCase
+import com.android.systemui.SysuiTestableContext
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -31,12 +38,29 @@ import dagger.Provides
FakeSystemUiModule::class,
]
)
-class SysUITestModule {
- @Provides fun provideContext(test: SysuiTestCase): Context = test.context
+interface SysUITestModule {
- @Provides @Application fun provideAppContext(test: SysuiTestCase): Context = test.context
+ @Binds fun bindTestableContext(sysuiTestableContext: SysuiTestableContext): TestableContext
+ @Binds fun bindContext(testableContext: TestableContext): Context
+ @Binds @Application fun bindAppContext(context: Context): Context
+ @Binds @Application fun bindAppResources(resources: Resources): Resources
+ @Binds @Main fun bindMainResources(resources: Resources): Resources
+ @Binds fun bindBroadcastDispatcher(fake: FakeBroadcastDispatcher): BroadcastDispatcher
- @Provides
- fun provideBroadcastDispatcher(test: SysuiTestCase): BroadcastDispatcher =
- test.fakeBroadcastDispatcher
+ companion object {
+ @Provides
+ fun provideSysuiTestableContext(test: SysuiTestCase): SysuiTestableContext = test.context
+
+ @Provides
+ fun provideTestableResources(context: TestableContext): TestableResources =
+ context.getOrCreateTestableResources()
+
+ @Provides
+ fun provideResources(testableResources: TestableResources): Resources =
+ testableResources.resources
+
+ @Provides
+ fun provideFakeBroadcastDispatcher(test: SysuiTestCase): FakeBroadcastDispatcher =
+ test.fakeBroadcastDispatcher
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
index 167e3417c162..f49ba646e0a1 100644
--- a/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
+++ b/packages/SystemUI/tests/src/com/android/TestMocksModule.kt
@@ -22,7 +22,9 @@ import android.util.DisplayMetrics
import com.android.internal.logging.MetricsLogger
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardViewController
import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.ScreenLifecycle
@@ -31,6 +33,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.BroadcastDispatcherLog
import com.android.systemui.log.dagger.SceneFrameworkLog
import com.android.systemui.media.controls.ui.MediaHierarchyManager
+import com.android.systemui.model.SysUiState
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -40,6 +43,7 @@ import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.collection.NotifCollection
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -72,6 +76,7 @@ data class TestMocksModule(
@get:Provides val keyguardSecurityModel: KeyguardSecurityModel = mock(),
@get:Provides val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(),
@get:Provides val mediaHierarchyManager: MediaHierarchyManager = mock(),
+ @get:Provides val notifCollection: NotifCollection = mock(),
@get:Provides val notificationListener: NotificationListener = mock(),
@get:Provides val notificationLockscreenUserManager: NotificationLockscreenUserManager = mock(),
@get:Provides val notificationMediaManager: NotificationMediaManager = mock(),
@@ -86,6 +91,9 @@ data class TestMocksModule(
@get:Provides val statusBarStateController: SysuiStatusBarStateController = mock(),
@get:Provides val statusBarWindowController: StatusBarWindowController = mock(),
@get:Provides val wakefulnessLifecycle: WakefulnessLifecycle = mock(),
+ @get:Provides val keyguardViewController: KeyguardViewController = mock(),
+ @get:Provides val dialogLaunchAnimator: DialogLaunchAnimator = mock(),
+ @get:Provides val sysuiState: SysUiState = mock(),
// log buffers
@get:[Provides BroadcastDispatcherLog]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
new file mode 100644
index 000000000000..b1421b21b377
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -0,0 +1,162 @@
+package com.android.systemui
+
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.hardware.camera2.CameraManager
+import android.testing.AndroidTestingRunner
+import android.util.PathParser
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.concurrent.Executor
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+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
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class CameraAvailabilityListenerTest : SysuiTestCase() {
+ companion object {
+ const val EXCLUDED_PKG = "test.excluded.package"
+ const val CAMERA_ID_FRONT = "0"
+ const val CAMERA_ID_INNER = "1"
+ const val PROTECTION_PATH_STRING_FRONT = "M 50,50 a 20,20 0 1 0 40,0 a 20,20 0 1 0 -40,0 Z"
+ const val PROTECTION_PATH_STRING_INNER = "M 40,40 a 10,10 0 1 0 20,0 a 10,10 0 1 0 -20,0 Z"
+ val PATH_RECT_FRONT = rectFromPath(pathFromString(PROTECTION_PATH_STRING_FRONT))
+ val PATH_RECT_INNER = rectFromPath(pathFromString(PROTECTION_PATH_STRING_INNER))
+
+ private fun pathFromString(pathString: String): Path {
+ val spec = pathString.trim()
+ val p: Path
+ try {
+ p = PathParser.createPathFromPathData(spec)
+ } catch (e: Throwable) {
+ throw IllegalArgumentException("Invalid protection path", e)
+ }
+
+ return p
+ }
+
+ private fun rectFromPath(path: Path): Rect {
+ val computed = RectF()
+ path.computeBounds(computed)
+ return Rect(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt()
+ )
+ }
+ }
+
+ @Mock private lateinit var cameraManager: CameraManager
+ @Mock
+ private lateinit var cameraTransitionCb: CameraAvailabilityListener.CameraTransitionCallback
+ private lateinit var cameraAvailabilityListener: CameraAvailabilityListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_cameraProtectionExcludedPackages, EXCLUDED_PKG)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_protectedCameraId, CAMERA_ID_FRONT)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ PROTECTION_PATH_STRING_FRONT
+ )
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_protectedInnerCameraId, CAMERA_ID_INNER)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ PROTECTION_PATH_STRING_INNER
+ )
+
+ context.addMockSystemService(CameraManager::class.java, cameraManager)
+
+ cameraAvailabilityListener =
+ CameraAvailabilityListener.Factory.build(context, context.mainExecutor)
+ }
+
+ @Test
+ fun testFrontCamera() {
+ var path: Path? = null
+ var rect: Rect? = null
+ val callback =
+ object : CameraAvailabilityListener.CameraTransitionCallback {
+ override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+ path = protectionPath
+ rect = bounds
+ }
+
+ override fun onHideCameraProtection() {}
+ }
+
+ cameraAvailabilityListener.addTransitionCallback(callback)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+
+ callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, "")
+ assertNotNull(path)
+ assertEquals(PATH_RECT_FRONT, rect)
+ }
+
+ @Test
+ fun testInnerCamera() {
+ var path: Path? = null
+ var rect: Rect? = null
+ val callback =
+ object : CameraAvailabilityListener.CameraTransitionCallback {
+ override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+ path = protectionPath
+ rect = bounds
+ }
+
+ override fun onHideCameraProtection() {}
+ }
+
+ cameraAvailabilityListener.addTransitionCallback(callback)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+
+ callbackCaptor.onCameraOpened(CAMERA_ID_INNER, "")
+ assertNotNull(path)
+ assertEquals(PATH_RECT_INNER, rect)
+ }
+
+ @Test
+ fun testExcludedPackage() {
+ cameraAvailabilityListener.addTransitionCallback(cameraTransitionCb)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+ callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, EXCLUDED_PKG)
+
+ verify(cameraTransitionCb, never())
+ .onApplyCameraProtection(any(Path::class.java), any(Rect::class.java))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 22ebd995b472..f15164e3fdcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.animation.ValueAnimator;
@@ -33,8 +34,8 @@ import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -46,13 +47,15 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
@@ -61,22 +64,18 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@LargeTest
@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
private static final float DEFAULT_SCALE = 4.0f;
private static final float DEFAULT_CENTER_X = 400.0f;
private static final float DEFAULT_CENTER_Y = 500.0f;
- // The duration and period couldn't too short, otherwise the ValueAnimator and
- // Instrumentation.runOnMainSync won't work in expectation. (b/288926821)
- private static final long ANIMATION_DURATION_MS = 600;
- private static final long WAIT_FULL_ANIMATION_PERIOD = 1000;
- private static final long WAIT_INTERMEDIATE_ANIMATION_PERIOD = 250;
private AtomicReference<Float> mCurrentScale = new AtomicReference<>((float) 0);
private AtomicReference<Float> mCurrentCenterX = new AtomicReference<>((float) 0);
@@ -105,10 +104,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
private WindowMagnificationController mSpyController;
private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
private Instrumentation mInstrumentation;
- private long mWaitingAnimationPeriod;
- private long mWaitIntermediateAnimationPeriod;
+ private long mWaitAnimationDuration;
+ private long mWaitPartialAnimationDuration;
private TestableWindowManager mWindowManager;
+ private ValueAnimator mValueAnimator;
@Before
public void setUp() throws Exception {
@@ -118,10 +118,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowManager = spy(new TestableWindowManager(wm));
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
- mWaitingAnimationPeriod = WAIT_FULL_ANIMATION_PERIOD;
- mWaitIntermediateAnimationPeriod = WAIT_INTERMEDIATE_ANIMATION_PERIOD;
+ // Using the animation duration in WindowMagnificationAnimationController for testing.
+ mWaitAnimationDuration = mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_longAnimTime);
+ mWaitPartialAnimationDuration = mWaitAnimationDuration / 2;
+
+ mValueAnimator = newValueAnimator();
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
- mContext, newValueAnimator());
+ mContext, mValueAnimator);
mController = new SpyWindowMagnificationController(mContext, mHandler,
mWindowMagnificationAnimationController,
mSfVsyncFrameProvider, null, new SurfaceControl.Transaction(),
@@ -131,13 +135,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@After
public void tearDown() throws Exception {
- mInstrumentation.runOnMainSync(() -> mController.deleteWindowMagnification());
+ mController.deleteWindowMagnification();
}
@Test
public void enableWindowMagnification_disabled_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -161,16 +165,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnificationWithoutCallback_enabled_expectedValues() {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
}
@@ -178,12 +179,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnificationWithScaleOne_disabled_NoAnimationAndInvokeCallback()
throws RemoteException {
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(1,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ mWindowMagnificationAnimationController.enableWindowMagnification(1,
+ DEFAULT_CENTER_X, DEFAULT_CENTER_Y, mAnimationCallback);
verify(mSpyController).enableWindowMagnificationInternal(1, DEFAULT_CENTER_X,
DEFAULT_CENTER_Y, 0f, 0f);
@@ -193,22 +190,19 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -226,24 +220,18 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnificationWithUnchanged_enabling_expectedValuesToDefault()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
-
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, animationCallback);
- });
-
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ throws RemoteException {
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
+
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+
// The callback in 2nd enableWindowMagnification will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in 1st enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@@ -256,16 +244,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -293,16 +278,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -331,11 +313,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
assertEquals(WindowMagnificationAnimationController.STATE_DISABLED,
@@ -346,17 +326,16 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
public void
enableMagnificationWithoutCallback_enabling_expectedValuesAndInvokeFormerCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE - 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
+
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
verify(mAnimationCallback).onResult(false);
}
@@ -364,15 +343,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnificationWithSameSpec_enabling_NoAnimationAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, mAnimationCallback2);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -383,28 +360,30 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnification_disabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+
// Current spec shouldn't match given spec.
verify(mAnimationCallback2, never()).onResult(anyBoolean());
verify(mAnimationCallback).onResult(false);
- SystemClock.sleep(mWaitingAnimationPeriod);
+ // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is
+ // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator
+ // directly to verify the result of animation is correct instead of querying the animation
+ // frame at a specific timing.
+ mValueAnimator.end();
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -424,18 +403,15 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
enableMagnificationWithoutCallback_disabling_expectedValuesAndInvokeFormerCallback()
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
final float targetScale = DEFAULT_SCALE + 1.0f;
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, null);
verify(mAnimationCallback).onResult(false);
verifyFinalSpec(targetScale, targetCenterX, targetCenterY);
@@ -444,16 +420,14 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnificationWithSameSpec_disabling_NoAnimationAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
- Float.NaN, Float.NaN, mAnimationCallback2);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(Float.NaN,
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -470,16 +444,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
- targetCenterX, targetCenterY, mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
-
- SystemClock.sleep(mWaitingAnimationPeriod);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(targetScale,
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ advanceTimeBy(mWaitAnimationDuration);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -498,129 +469,110 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
public void enableWindowMagnificationWithOffset_expectedValues() {
final float offsetRatio = -0.1f;
final Rect windowBounds = new Rect(mWindowManager.getCurrentWindowMetrics().getBounds());
- mInstrumentation.runOnMainSync(() -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- windowBounds.exactCenterX(), windowBounds.exactCenterY(),
- offsetRatio, offsetRatio, mAnimationCallback);
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
- final View attachedView = mWindowManager.getAttachedView();
- assertNotNull(attachedView);
- final Rect mirrorViewBound = new Rect();
- final View mirrorView = attachedView.findViewById(R.id.surface_view);
- assertNotNull(mirrorView);
- mirrorView.getBoundsOnScreen(mirrorViewBound);
-
- assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
- (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
- assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
- (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
-
- }
-
- @Test
- public void moveWindowMagnifierToPosition_enabled_expectedValues()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(1);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ windowBounds.exactCenterX(), windowBounds.exactCenterY(),
+ offsetRatio, offsetRatio, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
+
+ // We delay the time of verifying to wait for the measurement and layout of the view
+ mHandler.postDelayed(() -> {
+ final View attachedView = mWindowManager.getAttachedView();
+ assertNotNull(attachedView);
+ final Rect mirrorViewBound = new Rect();
+ final View mirrorView = attachedView.findViewById(R.id.surface_view);
+ assertNotNull(mirrorView);
+ mirrorView.getBoundsOnScreen(mirrorViewBound);
+
+ assertEquals((int) (offsetRatio * mirrorViewBound.width() / 2),
+ (int) (mirrorViewBound.exactCenterX() - windowBounds.exactCenterX()));
+ assertEquals((int) (offsetRatio * mirrorViewBound.height() / 2),
+ (int) (mirrorViewBound.exactCenterY() - windowBounds.exactCenterY()));
+ }, 100);
+ }
+
+ @Test
+ public void moveWindowMagnifierToPosition_enabled_expectedValues() throws RemoteException {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, animationCallback);
- });
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
- assertEquals(1, animationCallback.getSuccessCount());
- assertEquals(0, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(true);
+ verify(mAnimationCallback, never()).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
@Test
public void moveWindowMagnifierToPositionMultipleTimes_enabled_expectedValuesToLastOne()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(4);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, animationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, animationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, animationCallback);
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, animationCallback);
- });
-
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 10, DEFAULT_CENTER_Y + 10, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 20, DEFAULT_CENTER_Y + 20, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 30, DEFAULT_CENTER_Y + 30, mAnimationCallback);
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+
// only the last one callback will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// the others will return false
- assertEquals(3, animationCallback.getFailedCount());
+ verify(mAnimationCallback, times(3)).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + 40, DEFAULT_CENTER_Y + 40);
}
@Test
public void moveWindowMagnifierToPosition_enabling_expectedValuesToLastOne()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
+ throws RemoteException {
final float targetCenterX = DEFAULT_CENTER_X + 100;
final float targetCenterY = DEFAULT_CENTER_Y + 100;
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- targetCenterX, targetCenterY, animationCallback);
- });
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
+
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ targetCenterX, targetCenterY, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
// The callback in moveWindowMagnifierToPosition will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
@Test
public void moveWindowMagnifierToPositionWithCenterUnchanged_enabling_expectedValuesToDefault()
- throws InterruptedException {
- final CountDownLatch countDownLatch = new CountDownLatch(2);
- final MockMagnificationAnimationCallback animationCallback =
- new MockMagnificationAnimationCallback(countDownLatch);
-
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
- animationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
- Float.NaN, Float.NaN, animationCallback);
- });
-
- assertTrue(countDownLatch.await(mWaitingAnimationPeriod, TimeUnit.MILLISECONDS));
+ throws RemoteException {
+
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
+ mAnimationCallback);
+
+ mWindowMagnificationAnimationController.moveWindowMagnifierToPosition(
+ Float.NaN, Float.NaN, mAnimationCallback2);
+ advanceTimeBy(mWaitAnimationDuration);
+
// The callback in moveWindowMagnifierToPosition will return true
- assertEquals(1, animationCallback.getSuccessCount());
+ verify(mAnimationCallback2).onResult(true);
// The callback in enableWindowMagnification will return false
- assertEquals(1, animationCallback.getFailedCount());
+ verify(mAnimationCallback).onResult(false);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y);
}
@Test
public void enableWindowMagnificationWithSameScale_enabled_doNothingButInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
+ enableWindowMagnificationWithoutAnimation();
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ enableWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, never()).enableWindowMagnificationInternal(anyFloat(), anyFloat(),
anyFloat());
@@ -632,7 +584,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
throws RemoteException {
enableWindowMagnificationWithoutAnimation();
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
@@ -659,7 +611,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnification_disabled_doNothingAndInvokeCallback()
throws RemoteException {
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback);
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback);
Mockito.verifyNoMoreInteractions(mSpyController);
verify(mAnimationCallback).onResult(true);
@@ -668,20 +620,23 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.deleteWindowMagnification(
- mAnimationCallback2);
- mCurrentScale.set(mController.getScale());
- mCurrentCenterX.set(mController.getCenterX());
- mCurrentCenterY.set(mController.getCenterY());
- });
- SystemClock.sleep(mWaitingAnimationPeriod);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.deleteWindowMagnification(
+ mAnimationCallback2);
+ mCurrentScale.set(mController.getScale());
+ mCurrentCenterX.set(mController.getCenterX());
+ mCurrentCenterY.set(mController.getCenterY());
+ // ValueAnimator.reverse() could not work correctly with the AnimatorTestRule since it is
+ // using SystemClock in reverse() (b/305731398). Therefore, we call end() on the animator
+ // directly to verify the result of animation is correct instead of querying the animation
+ // frame at a specific timing.
+ mValueAnimator.end();
+
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -702,14 +657,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnificationWithoutCallback_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.deleteWindowMagnification(null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.deleteWindowMagnification(null);
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
verify(mAnimationCallback).onResult(false);
@@ -717,13 +669,13 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnification_disabling_checkStartAndValues() throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- deleteWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, mAnimationCallback2);
+ deleteWindowMagnificationAndWaitAnimating(mWaitAnimationDuration, mAnimationCallback2);
- verify(mSpyController, atLeast(2)).enableWindowMagnificationInternal(
+ verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
mOffsetXCaptor.capture(), mOffsetYCaptor.capture());
@@ -738,8 +690,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnificationWithoutCallback_disabling_checkStartAndValues()
throws RemoteException {
- enableWindowMagnificationAndWaitAnimating(mWaitingAnimationPeriod, null);
- deleteWindowMagnificationAndWaitAnimating(mWaitIntermediateAnimationPeriod,
+ enableWindowMagnificationWithoutAnimation();
+ deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
deleteWindowMagnificationAndWaitAnimating(0, null);
@@ -757,9 +709,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float offsetX = 50.0f;
final float offsetY =
(float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
- + 1.0f;
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifier(offsetX, offsetY));
+ + 1.0f;
+
+ mController.moveWindowMagnifier(offsetX, offsetY);
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X, DEFAULT_CENTER_Y + offsetY);
@@ -774,8 +726,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float offsetY =
(float) Math.floor(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE)
- 1.0f;
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifier(offsetX, offsetY));
+
+ mController.moveWindowMagnifier(offsetX, offsetY);
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y);
@@ -790,11 +742,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
(float) Math.ceil(offsetX * WindowMagnificationController.HORIZONTAL_LOCK_BASE);
// while diagonal scrolling enabled,
// should move with both offsetX and offsetY without regrading offsetY/offsetX
- mInstrumentation.runOnMainSync(
- () -> {
- mController.setDiagonalScrolling(true);
- mController.moveWindowMagnifier(offsetX, offsetY);
- });
+ mController.setDiagonalScrolling(true);
+ mController.moveWindowMagnifier(offsetX, offsetY);
verify(mSpyController).moveWindowMagnifier(offsetX, offsetY);
verifyFinalSpec(DEFAULT_SCALE, DEFAULT_CENTER_X + offsetX, DEFAULT_CENTER_Y + offsetY);
@@ -806,14 +755,17 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
final float targetCenterY = DEFAULT_CENTER_Y + 100;
enableWindowMagnificationWithoutAnimation();
- mInstrumentation.runOnMainSync(
- () -> mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
- mAnimationCallback));
- SystemClock.sleep(mWaitingAnimationPeriod);
+ mController.moveWindowMagnifierToPosition(targetCenterX, targetCenterY,
+ mAnimationCallback);
+ advanceTimeBy(mWaitAnimationDuration);
verifyFinalSpec(DEFAULT_SCALE, targetCenterX, targetCenterY);
}
+ private void advanceTimeBy(long timeDelta) {
+ mAnimatorTestRule.advanceTimeBy(timeDelta);
+ }
+
private void verifyFinalSpec(float expectedScale, float expectedCenterX,
float expectedCenterY) {
assertEquals(expectedScale, mController.getScale(), 0f);
@@ -822,33 +774,24 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
}
private void enableWindowMagnificationWithoutAnimation() {
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null);
- });
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ DEFAULT_CENTER_X, DEFAULT_CENTER_Y, null);
}
private void enableWindowMagnificationAndWaitAnimating(long duration,
@Nullable IRemoteMagnificationAnimationCallback callback) {
- mInstrumentation.runOnMainSync(
- () -> {
- Mockito.reset(mSpyController);
- mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
- DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
- });
- SystemClock.sleep(duration);
+ Mockito.reset(mSpyController);
+ mWindowMagnificationAnimationController.enableWindowMagnification(DEFAULT_SCALE,
+ DEFAULT_CENTER_X, DEFAULT_CENTER_Y, callback);
+ advanceTimeBy(duration);
}
private void deleteWindowMagnificationAndWaitAnimating(long duration,
@Nullable IRemoteMagnificationAnimationCallback callback) {
- mInstrumentation.runOnMainSync(
- () -> {
- resetMockObjects();
- mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
- });
- SystemClock.sleep(duration);
+ resetMockObjects();
+ mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
+ advanceTimeBy(duration);
}
private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
@@ -937,9 +880,9 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
}
}
- private static ValueAnimator newValueAnimator() {
+ private ValueAnimator newValueAnimator() {
final ValueAnimator valueAnimator = new ValueAnimator();
- valueAnimator.setDuration(ANIMATION_DURATION_MS);
+ valueAnimator.setDuration(mWaitAnimationDuration);
valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f));
valueAnimator.setFloatValues(0.0f, 1.0f);
return valueAnimator;
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 df7d1b7a4aad..aed795a440b4 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
@@ -222,14 +222,14 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Test
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
- final float menuTop = IME_TOP + 100;
- mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
+ mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100));
+ final PointF beforePosition = mMenuView.getMenuPosition();
dispatchShowingImeInsets();
final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
- assertThat(mMenuView.getTranslationX()).isEqualTo(0);
- assertThat(menuBottom).isLessThan(IME_TOP);
+ assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
+ assertThat(menuBottom).isLessThan(beforePosition.y);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
deleted file mode 100644
index 215d63508306..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.content.Context
-import android.hardware.biometrics.SensorProperties
-import android.hardware.fingerprint.FingerprintManager
-import android.hardware.fingerprint.FingerprintSensorProperties
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.view.ViewGroup.LayoutParams
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.airbnb.lottie.LottieAnimationView
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when` as whenEver
-import org.mockito.junit.MockitoJUnit
-
-private const val SENSOR_ID = 1
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AuthBiometricFingerprintIconControllerTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
- @Mock private lateinit var iconView: LottieAnimationView
- @Mock private lateinit var iconViewOverlay: LottieAnimationView
- @Mock private lateinit var layoutParam: LayoutParams
- @Mock private lateinit var fingerprintManager: FingerprintManager
-
- private lateinit var controller: AuthBiometricFingerprintIconController
-
- @Before
- fun setUp() {
- context.addMockSystemService(Context.FINGERPRINT_SERVICE, fingerprintManager)
- whenEver(iconView.layoutParams).thenReturn(layoutParam)
- whenEver(iconViewOverlay.layoutParams).thenReturn(layoutParam)
- }
-
- @Test
- fun testIconContentDescription_SfpsDevice() {
- setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
- controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
- assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
- .isEqualTo(
- context.resources.getString(
- R.string.security_settings_sfps_enroll_find_sensor_message
- )
- )
- }
-
- @Test
- fun testIconContentDescription_NonSfpsDevice() {
- setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
- controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
-
- assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
- .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
- }
-
- private fun setupFingerprintSensorProperties(sensorType: Int) {
- whenEver(fingerprintManager.sensorPropertiesInternal)
- .thenReturn(
- listOf(
- FingerprintSensorPropertiesInternal(
- SENSOR_ID,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- listOf() /* componentInfo */,
- sensorType,
- true /* halControlsIllumination */,
- true /* resetLockoutRequiresHardwareAuthToken */,
- listOf() /* sensorLocations */
- )
- )
- )
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index d68a3131da4c..8c26776a1ef8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -72,6 +72,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
runCurrent()
// WHEN shade expands
+ shadeRepository.setLegacyShadeTracking(true)
shadeRepository.setLegacyShadeExpansion(.5f)
runCurrent()
@@ -108,6 +109,7 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
// WHEN detector is disabled and shade opens
detector.disable()
+ shadeRepository.setLegacyShadeTracking(true)
shadeRepository.setLegacyShadeExpansion(.5f)
runCurrent()
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 9f24a9f553a1..15633d1baed1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -30,6 +30,7 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
internal fun fingerprintSensorPropertiesInternal(
ids: List<Int> = listOf(0),
strong: Boolean = true,
+ sensorType: Int = FingerprintSensorProperties.TYPE_REAR
): List<FingerprintSensorPropertiesInternal> {
val componentInfo =
listOf(
@@ -54,7 +55,7 @@ internal fun fingerprintSensorPropertiesInternal(
if (strong) SensorProperties.STRENGTH_STRONG else SensorProperties.STRENGTH_WEAK,
5 /* maxEnrollmentsPerUser */,
componentInfo,
- FingerprintSensorProperties.TYPE_REAR,
+ sensorType,
false /* resetLockoutRequiresHardwareAuthToken */
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
deleted file mode 100644
index fd86486aeff8..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.android.systemui.biometrics.ui.viewmodel
-
-import android.content.res.Configuration
-import androidx.test.filters.SmallTest
-import com.android.internal.widget.LockPatternUtils
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
-import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
-import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
-import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.shared.model.FingerprintSensorType
-import com.android.systemui.biometrics.shared.model.SensorStrength
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.display.data.repository.FakeDisplayRepository
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
-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.Mock
-import org.mockito.junit.MockitoJUnit
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class PromptFingerprintIconViewModelTest : SysuiTestCase() {
-
- @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-
- @Mock private lateinit var lockPatternUtils: LockPatternUtils
-
- private lateinit var displayRepository: FakeDisplayRepository
- private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
- private lateinit var promptRepository: FakePromptRepository
- private lateinit var displayStateRepository: FakeDisplayStateRepository
-
- private val testScope = TestScope(StandardTestDispatcher())
- private val fakeExecutor = FakeExecutor(FakeSystemClock())
-
- private lateinit var promptSelectorInteractor: PromptSelectorInteractor
- private lateinit var displayStateInteractor: DisplayStateInteractor
- private lateinit var viewModel: PromptFingerprintIconViewModel
-
- @Before
- fun setup() {
- displayRepository = FakeDisplayRepository()
- fingerprintRepository = FakeFingerprintPropertyRepository()
- promptRepository = FakePromptRepository()
- displayStateRepository = FakeDisplayStateRepository()
-
- promptSelectorInteractor =
- PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
- displayStateInteractor =
- DisplayStateInteractorImpl(
- testScope.backgroundScope,
- mContext,
- fakeExecutor,
- displayStateRepository,
- displayRepository,
- )
- viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
- }
-
- @Test
- fun sfpsIconUpdates_onConfigurationChanged() {
- testScope.runTest {
- runCurrent()
- configureFingerprintPropertyRepository(FingerprintSensorType.POWER_BUTTON)
- val testConfig = Configuration()
- val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
- val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
- val currentIcon = collectLastValue(viewModel.iconAsset)
-
- testConfig.smallestScreenWidthDp = folded
- viewModel.onConfigurationChanged(testConfig)
- val foldedIcon = currentIcon()
-
- testConfig.smallestScreenWidthDp = unfolded
- viewModel.onConfigurationChanged(testConfig)
- val unfoldedIcon = currentIcon()
-
- assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
- }
- }
-
- private fun configureFingerprintPropertyRepository(sensorType: FingerprintSensorType) {
- fingerprintRepository.setProperties(0, SensorStrength.STRONG, sensorType, mapOf())
- }
-}
-
-internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index ca6df4027ea9..b695a0ee1fa3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.content.res.Configuration
import android.hardware.biometrics.PromptInfo
import android.hardware.face.FaceSensorPropertiesInternal
+import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
@@ -36,12 +38,15 @@ import com.android.systemui.biometrics.faceSensorPropertiesInternal
import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
-import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
+import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -66,6 +71,7 @@ import org.mockito.junit.MockitoJUnit
private const val USER_ID = 4
private const val CHALLENGE = 2L
+private const val DELAY = 1000L
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -88,11 +94,22 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
private lateinit var selector: PromptSelectorInteractor
private lateinit var viewModel: PromptViewModel
+ private lateinit var iconViewModel: PromptIconViewModel
private val featureFlags = FakeFeatureFlags()
@Before
fun setup() {
fingerprintRepository = FakeFingerprintPropertyRepository()
+ testCase.fingerprint?.let {
+ fingerprintRepository.setProperties(
+ it.sensorId,
+ it.sensorStrength.toSensorStrength(),
+ it.sensorType.toSensorType(),
+ it.allLocations.associateBy { sensorLocationInternal ->
+ sensorLocationInternal.displayId
+ }
+ )
+ }
promptRepository = FakePromptRepository()
displayStateRepository = FakeDisplayStateRepository()
displayRepository = FakeDisplayRepository()
@@ -110,6 +127,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
viewModel =
PromptViewModel(displayStateInteractor, selector, vibrator, mContext, featureFlags)
+ iconViewModel = viewModel.iconViewModel
featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
}
@@ -123,7 +141,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val modalities by collectLastValue(viewModel.modalities)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
assertThat(authenticating).isFalse()
assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -133,7 +150,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
assertThat(message).isEqualTo(PromptMessage.Empty)
assertThat(size).isEqualTo(expectedSize)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE)
val startMessage = "here we go"
viewModel.showAuthenticating(startMessage, isRetry = false)
@@ -143,7 +159,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(authenticated?.isNotAuthenticated).isTrue()
assertThat(size).isEqualTo(expectedSize)
assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
}
@Test
@@ -205,6 +220,472 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
}
+ @Test
+ fun start_idle_and_show_authenticating_iconUpdate() =
+ runGenericTest(doNotStart = true) {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+ if (forceExplicitFlow) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+
+ val startMessage = "here we go"
+ viewModel.showAuthenticating(startMessage, isRetry = false)
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(false)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
+ assertThat(iconAsset).isEqualTo(expectedIconAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
+ }
+
+ if (testCase.isCoex) {
+ if (testCase.confirmationRequested || forceExplicitFlow) {
+ // explicit flow
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(false)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ } else {
+ // implicit flow
+ val shouldRepeatAnimation by
+ collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
+ assertThat(iconAsset).isEqualTo(expectedIconAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
+ }
+ }
+ }
+
+ @Test
+ fun start_authenticating_show_and_clear_error_iconUpdate() = runGenericTest {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ val forceExplicitFlow = testCase.isCoex && testCase.authenticatedByFingerprint
+ if (forceExplicitFlow) {
+ viewModel.ensureFingerprintHasStarted(isDelayed = true)
+ }
+
+ val errorJob = launch {
+ viewModel.showTemporaryError(
+ "so sad",
+ messageAfterError = "",
+ authenticateAfterError = testCase.isFingerprintOnly || testCase.isCoex,
+ )
+ // Usually done by binder
+ iconViewModel.setPreviousIconWasError(true)
+ iconViewModel.setPreviousIconOverlayWasError(true)
+ }
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_fingerprint_to_error_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+
+ // Clear error, restart authenticating
+ errorJob.join()
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+
+ // Clear error, go to idle
+ errorJob.join()
+
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_error_to_idle)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_error_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId).isEqualTo(R.string.biometric_dialog_try_again)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+
+ // Clear error, restart authenticating
+ errorJob.join()
+
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_error_to_fingerprint_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun shows_authenticated_no_errors_no_confirmation_required_iconUpdate() = runGenericTest {
+ if (!testCase.confirmationRequested) {
+ val currentRotation by collectLastValue(displayStateInteractor.currentRotation)
+
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ if (testCase.isFingerprintOnly) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val expectedOverlayAsset =
+ when (currentRotation) {
+ DisplayRotation.ROTATION_0 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_90 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft
+ DisplayRotation.ROTATION_180 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_landscape
+ DisplayRotation.ROTATION_270 ->
+ R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright
+ else -> throw Exception("invalid rotation")
+ }
+ assertThat(iconOverlayAsset).isEqualTo(expectedOverlayAsset)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.security_settings_sfps_enroll_find_sensor_message)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(true)
+ } else {
+ val isAuthenticated by collectLastValue(viewModel.isAuthenticated)
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_success_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+
+ // If co-ex, using implicit flow (explicit flow always requires confirmation)
+ if (testCase.isFaceOnly || testCase.isCoex) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+ }
+ }
+
+ @Test
+ fun shows_pending_confirmation_iconUpdate() = runGenericTest {
+ if (
+ (testCase.isFaceOnly || testCase.isCoex) &&
+ testCase.authenticatedByFace &&
+ testCase.confirmationRequested
+ ) {
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ // explicit flow because confirmation requested
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_authenticated_confirmation)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun shows_authenticated_explicitly_confirmed_iconUpdate() = runGenericTest {
+ if (
+ (testCase.isFaceOnly || testCase.isCoex) &&
+ testCase.authenticatedByFace &&
+ testCase.confirmationRequested
+ ) {
+ val iconAsset by collectLastValue(iconViewModel.iconAsset)
+ val iconContentDescriptionId by collectLastValue(iconViewModel.contentDescriptionId)
+ val shouldAnimateIconView by collectLastValue(iconViewModel.shouldAnimateIconView)
+
+ viewModel.showAuthenticated(
+ modality = testCase.authenticatedModality,
+ dismissAfterDelay = DELAY
+ )
+
+ viewModel.confirmAuthenticated()
+
+ if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
+ assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
+ }
+
+ // explicit flow because confirmation requested
+ if (testCase.isCoex) {
+ val iconOverlayAsset by collectLastValue(iconViewModel.iconOverlayAsset)
+ val shouldAnimateIconOverlay by
+ collectLastValue(iconViewModel.shouldAnimateIconOverlay)
+
+ // TODO: Update when SFPS co-ex is implemented
+ if (testCase.sensorType != FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ assertThat(iconAsset)
+ .isEqualTo(R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie)
+ assertThat(iconOverlayAsset).isEqualTo(-1)
+ assertThat(iconContentDescriptionId)
+ .isEqualTo(R.string.fingerprint_dialog_touch_sensor)
+ assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldAnimateIconOverlay).isEqualTo(false)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onConfigurationChanged() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val testConfig = Configuration()
+ val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
+ val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ testConfig.smallestScreenWidthDp = folded
+ iconViewModel.onConfigurationChanged(testConfig)
+ val foldedIcon = currentIcon
+
+ testConfig.smallestScreenWidthDp = unfolded
+ iconViewModel.onConfigurationChanged(testConfig)
+ val unfoldedIcon = currentIcon
+
+ assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onRotation() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val iconRotation0 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+ val iconRotation90 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val iconRotation180 = currentIcon
+
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ val iconRotation270 = currentIcon
+
+ assertThat(iconRotation0).isEqualTo(iconRotation180)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation90)
+ assertThat(iconRotation0).isNotEqualTo(iconRotation270)
+ }
+ }
+
+ @Test
+ fun sfpsIconUpdates_onRearDisplayMode() = runGenericTest {
+ if (testCase.sensorType == FingerprintSensorProperties.TYPE_POWER_BUTTON) {
+ val currentIcon by collectLastValue(iconViewModel.iconAsset)
+
+ displayStateRepository.setIsInRearDisplayMode(false)
+ val iconNotRearDisplayMode = currentIcon
+
+ displayStateRepository.setIsInRearDisplayMode(true)
+ val iconRearDisplayMode = currentIcon
+
+ assertThat(iconNotRearDisplayMode).isNotEqualTo(iconRearDisplayMode)
+ }
+ }
+
private suspend fun TestScope.showAuthenticated(
authenticatedModality: BiometricModality,
expectConfirmation: Boolean,
@@ -213,7 +694,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val authenticated by collectLastValue(viewModel.isAuthenticated)
val fpStartMode by collectLastValue(viewModel.fingerprintStartMode)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val authWithSmallPrompt =
testCase.shouldStartAsImplicitFlow &&
@@ -221,14 +701,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(authenticating).isTrue()
assertThat(authenticated?.isNotAuthenticated).isTrue()
assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
assertButtonsVisible(negative = !authWithSmallPrompt)
- val delay = 1000L
- viewModel.showAuthenticated(authenticatedModality, delay)
+ viewModel.showAuthenticated(authenticatedModality, DELAY)
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(authenticated?.delay).isEqualTo(delay)
+ assertThat(authenticated?.delay).isEqualTo(DELAY)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
assertThat(size)
.isEqualTo(
@@ -238,14 +716,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
PromptSize.SMALL
}
)
- assertThat(legacyState)
- .isEqualTo(
- if (expectConfirmation) {
- BiometricState.STATE_PENDING_CONFIRMATION
- } else {
- BiometricState.STATE_AUTHENTICATED
- }
- )
+
assertButtonsVisible(
cancel = expectConfirmation,
confirm = expectConfirmation,
@@ -298,7 +769,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgainNow by collectLastValue(viewModel.canTryAgainNow)
val errorJob = launch {
@@ -312,7 +782,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(size).isEqualTo(PromptSize.MEDIUM)
assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
assertThat(messageVisible).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR)
// temporary error should disappear after a delay
errorJob.join()
@@ -323,17 +792,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(message).isEqualTo(PromptMessage.Empty)
assertThat(messageVisible).isFalse()
}
- val clearIconError = !restart
- assertThat(legacyState)
- .isEqualTo(
- if (restart) {
- BiometricState.STATE_AUTHENTICATING
- } else if (clearIconError) {
- BiometricState.STATE_IDLE
- } else {
- BiometricState.STATE_HELP
- }
- )
assertThat(authenticating).isEqualTo(restart)
assertThat(authenticated?.isNotAuthenticated).isTrue()
@@ -488,7 +946,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -506,7 +963,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(authenticating).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
assertThat(canTryAgain).isFalse()
}
@@ -524,7 +980,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticating).isFalse()
@@ -532,8 +987,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(authenticated?.isAuthenticated).isTrue()
if (testCase.isFaceOnly && expectConfirmation) {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
-
assertThat(size).isEqualTo(PromptSize.MEDIUM)
assertButtonsVisible(
cancel = true,
@@ -543,8 +996,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
viewModel.confirmAuthenticated()
assertThat(message).isEqualTo(PromptMessage.Empty)
assertButtonsVisible()
- } else {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
}
}
@@ -563,7 +1014,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val authenticated by collectLastValue(viewModel.isAuthenticated)
val message by collectLastValue(viewModel.message)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val canTryAgain by collectLastValue(viewModel.canTryAgainNow)
assertThat(authenticated?.needsUserConfirmation).isEqualTo(expectConfirmation)
@@ -581,7 +1031,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(authenticating).isFalse()
assertThat(authenticated?.isAuthenticated).isTrue()
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
assertThat(canTryAgain).isFalse()
}
@@ -610,12 +1059,10 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP)
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
@@ -632,7 +1079,6 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
val message by collectLastValue(viewModel.message)
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
- val legacyState by collectLastValue(viewModel.legacyState)
val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
if (testCase.isCoex && testCase.authenticatedByFingerprint) {
@@ -642,11 +1088,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- if (confirmationRequired == true) {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
- } else {
- assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
- }
+
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
assertThat(authenticating).isFalse()
@@ -785,6 +1227,15 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
authenticatedModality = BiometricModality.Fingerprint,
),
TestCase(
+ fingerprint =
+ fingerprintSensorPropertiesInternal(
+ strong = true,
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ )
+ .first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ ),
+ TestCase(
face = faceSensorPropertiesInternal(strong = true).first(),
authenticatedModality = BiometricModality.Face,
confirmationRequested = true,
@@ -794,6 +1245,16 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
authenticatedModality = BiometricModality.Fingerprint,
confirmationRequested = true,
),
+ TestCase(
+ fingerprint =
+ fingerprintSensorPropertiesInternal(
+ strong = true,
+ sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+ )
+ .first(),
+ authenticatedModality = BiometricModality.Fingerprint,
+ confirmationRequested = true,
+ ),
)
private val coexTestCases =
@@ -834,7 +1295,9 @@ internal data class TestCase(
val modality =
when {
fingerprint != null && face != null -> "coex"
- fingerprint != null -> "fingerprint only"
+ fingerprint != null && fingerprint.isAnySidefpsType -> "fingerprint only, sideFps"
+ fingerprint != null && !fingerprint.isAnySidefpsType ->
+ "fingerprint only, non-sideFps"
face != null -> "face only"
else -> "?"
}
@@ -864,6 +1327,8 @@ internal data class TestCase(
val isCoex: Boolean
get() = face != null && fingerprint != null
+ @FingerprintSensorProperties.SensorType val sensorType: Int? = fingerprint?.sensorType
+
val shouldStartAsImplicitFlow: Boolean
get() = (isFaceOnly || isCoex) && !confirmationRequested
}
@@ -890,3 +1355,5 @@ private fun PromptSelectorInteractor.initializePrompt(
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
)
}
+
+internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index 9373ada75003..f6b284fffa3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -111,6 +111,27 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
}
@Test
+ fun show_nullDelegate() {
+ testScope.run {
+ whenever(bouncerView.delegate).thenReturn(null)
+ mainHandler.setMode(FakeHandler.Mode.QUEUEING)
+
+ // WHEN bouncer show is requested
+ underTest.show(true)
+
+ // WHEN all queued messages are dispatched
+ mainHandler.dispatchQueuedMessages()
+
+ // THEN primary bouncer state doesn't update to show since delegate was null
+ verify(repository, never()).setPrimaryShow(true)
+ verify(repository, never()).setPrimaryShowingSoon(false)
+ verify(mPrimaryBouncerCallbackInteractor, never()).dispatchStartingToShow()
+ verify(mPrimaryBouncerCallbackInteractor, never())
+ .dispatchVisibilityChanged(View.VISIBLE)
+ }
+ }
+
+ @Test
fun testShow_isScrimmed() {
underTest.show(true)
verify(repository).setKeyguardAuthenticatedBiometrics(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
index 80b281e458d0..882bcab5fab6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java
@@ -17,7 +17,6 @@
package com.android.systemui.colorextraction;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -105,21 +104,6 @@ public class SysuiColorExtractorTests extends SysuiTestCase {
}
@Test
- public void getColors_fallbackWhenMediaIsVisible() {
- simulateEvent(mColorExtractor);
- mColorExtractor.setHasMediaArtwork(true);
-
- ColorExtractor.GradientColors fallbackColors = mColorExtractor.getNeutralColors();
-
- for (int type : sTypes) {
- assertEquals("Not using fallback!",
- mColorExtractor.getColors(WallpaperManager.FLAG_LOCK, type), fallbackColors);
- assertNotEquals("Media visibility should not affect system wallpaper.",
- mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, type), fallbackColors);
- }
- }
-
- @Test
public void onUiModeChanged_reloadsColors() {
Tonal tonal = mock(Tonal.class);
ConfigurationController configurationController = mock(ConfigurationController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 91409a376556..fcb191b4cbd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -12,9 +12,10 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.FakeLogBuffer
@@ -38,6 +39,7 @@ import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -58,10 +60,16 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var featureFlags: FeatureFlagsClassic
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
+ @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
+
+ @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
+
+ @Mock private lateinit var providerInfoC: AppWidgetProviderInfo
+
private lateinit var communalRepository: FakeCommunalRepository
private lateinit var logBuffer: LogBuffer
@@ -70,29 +78,34 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
private val testScope = TestScope(testDispatcher)
+ private val fakeAllowlist =
+ listOf(
+ "com.android.fake/WidgetProviderA",
+ "com.android.fake/WidgetProviderB",
+ "com.android.fake/WidgetProviderC",
+ )
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
logBuffer = FakeLogBuffer.Factory.create()
-
- featureFlagEnabled(true)
communalRepository = FakeCommunalRepository()
- communalRepository.setIsCommunalEnabled(true)
- overrideResource(
- R.array.config_communalWidgetAllowlist,
- arrayOf(componentName1, componentName2)
- )
+ communalEnabled(true)
+ widgetOnKeyguardEnabled(true)
+ setAppWidgetIds(emptyList())
+
+ overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
whenever(userTracker.userHandle).thenReturn(userHandle)
}
@Test
- fun broadcastReceiver_featureDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+ fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
testScope.runTest {
- featureFlagEnabled(false)
+ communalEnabled(false)
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
verifyBroadcastReceiverNeverRegistered()
@@ -129,7 +142,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
job.cancel()
runCurrent()
- Mockito.verify(broadcastDispatcher).unregisterReceiver(receiver)
+ verify(broadcastDispatcher).unregisterReceiver(receiver)
}
@Test
@@ -166,7 +179,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
installedProviders(listOf(stopwatchProviderInfo))
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).allocateAppWidgetId()
+ verify(appWidgetHost).allocateAppWidgetId()
}
@Test
@@ -185,8 +198,8 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
// Verify app widget id allocated
assertThat(lastStopwatchProviderInfo()?.appWidgetId).isEqualTo(123456)
- Mockito.verify(appWidgetHost).allocateAppWidgetId()
- Mockito.verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
+ verify(appWidgetHost).allocateAppWidgetId()
+ verify(appWidgetHost, Mockito.never()).deleteAppWidgetId(anyInt())
// User locked again
userUnlocked(false)
@@ -194,7 +207,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
// Verify app widget id deleted
assertThat(lastStopwatchProviderInfo()).isNull()
- Mockito.verify(appWidgetHost).deleteAppWidgetId(123456)
+ verify(appWidgetHost).deleteAppWidgetId(123456)
}
@Test
@@ -203,13 +216,13 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
userUnlocked(false)
val repository = initCommunalWidgetRepository()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost, Mockito.never()).startListening()
+ verify(appWidgetHost, Mockito.never()).startListening()
userUnlocked(true)
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).startListening()
+ verify(appWidgetHost).startListening()
}
@Test
@@ -223,14 +236,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).startListening()
- Mockito.verify(appWidgetHost, Mockito.never()).stopListening()
+ verify(appWidgetHost).startListening()
+ verify(appWidgetHost, Mockito.never()).stopListening()
userUnlocked(false)
broadcastReceiverUpdate()
collectLastValue(repository.stopwatchAppWidgetInfo)()
- Mockito.verify(appWidgetHost).stopListening()
+ verify(appWidgetHost).stopListening()
}
@Test
@@ -241,21 +254,80 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
assertThat(
listOf(
CommunalWidgetMetadata(
- componentName = componentName1,
+ componentName = fakeAllowlist[0],
+ priority = 3,
+ sizes = listOf(CommunalContentSize.HALF),
+ ),
+ CommunalWidgetMetadata(
+ componentName = fakeAllowlist[1],
priority = 2,
- sizes = listOf(CommunalContentSize.HALF)
+ sizes = listOf(CommunalContentSize.HALF),
),
CommunalWidgetMetadata(
- componentName = componentName2,
+ componentName = fakeAllowlist[2],
priority = 1,
- sizes = listOf(CommunalContentSize.HALF)
- )
+ sizes = listOf(CommunalContentSize.HALF),
+ ),
)
)
.containsExactly(*communalWidgetAllowlist.toTypedArray())
}
}
+ // This behavior is temporary before the local database is set up.
+ @Test
+ fun communalWidgets_withPreviouslyBoundWidgets_removeEachBinding() =
+ testScope.runTest {
+ whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(1, 2, 3)
+ setAppWidgetIds(listOf(1, 2, 3))
+ whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
+ userUnlocked(true)
+
+ val repository = initCommunalWidgetRepository()
+
+ collectLastValue(repository.communalWidgets)()
+
+ verify(appWidgetHost).deleteAppWidgetId(1)
+ verify(appWidgetHost).deleteAppWidgetId(2)
+ verify(appWidgetHost).deleteAppWidgetId(3)
+ }
+
+ @Test
+ fun communalWidgets_allowlistNotEmpty_bindEachWidgetFromTheAllowlist() =
+ testScope.runTest {
+ whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(0, 1, 2)
+ userUnlocked(true)
+
+ whenever(appWidgetManager.getAppWidgetInfo(0)).thenReturn(providerInfoA)
+ whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfoB)
+ whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfoC)
+
+ val repository = initCommunalWidgetRepository()
+
+ val inventory by collectLastValue(repository.communalWidgets)
+
+ assertThat(
+ listOf(
+ CommunalWidgetContentModel(
+ appWidgetId = 0,
+ providerInfo = providerInfoA,
+ priority = 3,
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 1,
+ providerInfo = providerInfoB,
+ priority = 2,
+ ),
+ CommunalWidgetContentModel(
+ appWidgetId = 2,
+ providerInfo = providerInfoC,
+ priority = 1,
+ ),
+ )
+ )
+ .containsExactly(*inventory!!.toTypedArray())
+ }
+
private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
return CommunalWidgetRepositoryImpl(
context,
@@ -272,7 +344,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
}
private fun verifyBroadcastReceiverRegistered() {
- Mockito.verify(broadcastDispatcher)
+ verify(broadcastDispatcher)
.registerReceiver(
any(),
any(),
@@ -284,7 +356,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
}
private fun verifyBroadcastReceiverNeverRegistered() {
- Mockito.verify(broadcastDispatcher, Mockito.never())
+ verify(broadcastDispatcher, Mockito.never())
.registerReceiver(
any(),
any(),
@@ -297,7 +369,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
private fun broadcastReceiverUpdate(): BroadcastReceiver {
val broadcastReceiverCaptor = kotlinArgumentCaptor<BroadcastReceiver>()
- Mockito.verify(broadcastDispatcher)
+ verify(broadcastDispatcher)
.registerReceiver(
broadcastReceiverCaptor.capture(),
any(),
@@ -310,7 +382,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
return broadcastReceiverCaptor.value
}
- private fun featureFlagEnabled(enabled: Boolean) {
+ private fun communalEnabled(enabled: Boolean) {
+ communalRepository.setIsCommunalEnabled(enabled)
+ }
+
+ private fun widgetOnKeyguardEnabled(enabled: Boolean) {
whenever(featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)).thenReturn(enabled)
}
@@ -322,8 +398,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
- companion object {
- const val componentName1 = "component name 1"
- const val componentName2 = "component name 2"
+ private fun setAppWidgetIds(ids: List<Int>) {
+ whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cdc42e096830..8e21f294a361 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -22,7 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
index 0e14591c5f53..52c6e22cfcbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -18,9 +18,11 @@ package com.android.systemui.flags
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -61,80 +63,70 @@ class ConditionalRestarterTest : SysuiTestCase() {
@Test
fun restart_ImmediatelySatisfied() =
testScope.runTest {
- conditionA.canRestart = true
- conditionB.canRestart = true
+ conditionA.canRestart.emit(true)
+ conditionB.canRestart.emit(true)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
@Test
fun restart_WaitsForConditionA() =
testScope.runTest {
- conditionA.canRestart = false
- conditionB.canRestart = true
+ conditionA.canRestart.emit(false)
+ conditionB.canRestart.emit(true)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
- conditionA.canRestart = true
- conditionA.retryFn?.invoke()
- advanceUntilIdle()
+ conditionA.canRestart.emit(true)
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
@Test
fun restart_WaitsForConditionB() =
testScope.runTest {
- conditionA.canRestart = true
- conditionB.canRestart = false
+ conditionA.canRestart.emit(true)
+ conditionB.canRestart.emit(false)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
- conditionB.canRestart = true
- conditionB.retryFn?.invoke()
- advanceUntilIdle()
+ conditionB.canRestart.emit(true)
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
@Test
fun restart_WaitsForAllConditions() =
testScope.runTest {
- conditionA.canRestart = true
- conditionB.canRestart = false
+ conditionA.canRestart.emit(true)
+ conditionB.canRestart.emit(false)
restarter.restartSystemUI("Restart for test")
- advanceUntilIdle()
+ runCurrent()
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
// B becomes true, but A is now false
- conditionA.canRestart = false
- conditionB.canRestart = true
- conditionB.retryFn?.invoke()
- advanceUntilIdle()
+ conditionA.canRestart.emit(false)
+ conditionB.canRestart.emit(true)
// No restart occurs yet.
verify(systemExitRestarter, never()).restartSystemUI(any())
- conditionA.canRestart = true
- conditionA.retryFn?.invoke()
- advanceUntilIdle()
+ conditionA.canRestart.emit(true)
+ runCurrent()
verify(systemExitRestarter).restartSystemUI(any())
}
class FakeCondition : ConditionalRestarter.Condition {
- var retryFn: (() -> Unit)? = null
- var canRestart = false
+ val canRestart = MutableStateFlow(false)
- override fun canRestartNow(retryFn: () -> Unit): Boolean {
- this.retryFn = retryFn
-
- return canRestart
- }
+ override val canRestartNow: Flow<Boolean> = canRestart
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
new file mode 100644
index 000000000000..db6f85f12a42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/NotOccludedConditionTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class NotOccludedConditionTest : SysuiTestCase() {
+ private lateinit var condition: NotOccludedCondition
+
+ @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private val transitionValue = MutableStateFlow(0f)
+
+ private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testScope: TestScope = TestScope(testDispatcher)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ whenever(keyguardTransitionInteractor.transitionValue(KeyguardState.OCCLUDED))
+ .thenReturn(transitionValue)
+ condition = NotOccludedCondition({ keyguardTransitionInteractor })
+ testScope.runCurrent()
+ }
+
+ @Test
+ fun testCondition_occluded() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ transitionValue.emit(1f)
+ assertThat(canRestart).isFalse()
+ }
+
+ @Test
+ fun testCondition_notOccluded() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ transitionValue.emit(0f)
+ assertThat(canRestart).isTrue()
+ }
+
+ @Test
+ fun testCondition_invokesRetry() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ transitionValue.emit(1f)
+
+ assertThat(canRestart).isFalse()
+
+ transitionValue.emit(0f)
+
+ assertThat(canRestart).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
index 647b05a77b90..7d7abab4a0f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -17,8 +17,13 @@ package com.android.systemui.flags
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.policy.BatteryController
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
@@ -35,42 +40,51 @@ class PluggedInConditionTest : SysuiTestCase() {
private lateinit var condition: PluggedInCondition
@Mock private lateinit var batteryController: BatteryController
+ private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testScope: TestScope = TestScope(testDispatcher)
+ private val callbackCaptor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- condition = PluggedInCondition(batteryController)
+
+ condition = PluggedInCondition({ batteryController })
}
@Test
- fun testCondition_unplugged() {
- whenever(batteryController.isPluggedIn).thenReturn(false)
+ fun testCondition_unplugged() =
+ testScope.runTest {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
- assertThat(condition.canRestartNow({})).isFalse()
- }
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ assertThat(canRestart).isFalse()
+ }
@Test
- fun testCondition_pluggedIn() {
- whenever(batteryController.isPluggedIn).thenReturn(true)
+ fun testCondition_pluggedIn() =
+ testScope.runTest {
+ whenever(batteryController.isPluggedIn).thenReturn(true)
- assertThat(condition.canRestartNow({})).isTrue()
- }
+ val canRestart by collectLastValue(condition.canRestartNow)
+
+ assertThat(canRestart).isTrue()
+ }
@Test
- fun testCondition_invokesRetry() {
- whenever(batteryController.isPluggedIn).thenReturn(false)
- var retried = false
- val retryFn = { retried = true }
+ fun testCondition_invokesRetry() =
+ testScope.runTest {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
- // No restart yet, but we do register a listener now.
- assertThat(condition.canRestartNow(retryFn)).isFalse()
- val captor =
- ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
- verify(batteryController).addCallback(captor.capture())
+ val canRestart by collectLastValue(condition.canRestartNow)
- whenever(batteryController.isPluggedIn).thenReturn(true)
+ assertThat(canRestart).isFalse()
- captor.value.onBatteryLevelChanged(0, true, true)
- assertThat(retried).isTrue()
- }
+ verify(batteryController).addCallback(callbackCaptor.capture())
+
+ callbackCaptor.value.onBatteryLevelChanged(0, true, false)
+
+ assertThat(canRestart).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index f7a773ea30ec..1f04828c359a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -17,15 +17,17 @@ package com.android.systemui.flags
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
-import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -36,42 +38,50 @@ import org.mockito.MockitoAnnotations
class ScreenIdleConditionTest : SysuiTestCase() {
private lateinit var condition: ScreenIdleCondition
- @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var powerInteractor: PowerInteractor
+ private val isAsleep = MutableStateFlow(false)
+
+ private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+ private val testScope: TestScope = TestScope(testDispatcher)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- condition = ScreenIdleCondition(wakefulnessLifecycle)
+ whenever(powerInteractor.isAsleep).thenReturn(isAsleep)
+ condition = ScreenIdleCondition({ powerInteractor })
}
@Test
- fun testCondition_awake() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ fun testCondition_awake() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
- assertThat(condition.canRestartNow {}).isFalse()
- }
+ isAsleep.emit(false)
+
+ assertThat(canRestart).isFalse()
+ }
@Test
- fun testCondition_asleep() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ fun testCondition_asleep() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
- assertThat(condition.canRestartNow {}).isTrue()
- }
+ isAsleep.emit(true)
+
+ assertThat(canRestart).isTrue()
+ }
@Test
- fun testCondition_invokesRetry() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- var retried = false
- val retryFn = { retried = true }
+ fun testCondition_invokesRetry() =
+ testScope.runTest {
+ val canRestart by collectLastValue(condition.canRestartNow)
- // No restart yet, but we do register a listener now.
- assertThat(condition.canRestartNow(retryFn)).isFalse()
- val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
- verify(wakefulnessLifecycle).addObserver(captor.capture())
+ isAsleep.emit(false)
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ assertThat(canRestart).isFalse()
- captor.value.onFinishedGoingToSleep()
- assertThat(retried).isTrue()
- }
+ isAsleep.emit(true)
+
+ assertThat(canRestart).isTrue()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
index bb6786ade21a..e16b8d400149 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/SetFlagsRuleExtensions.kt
@@ -18,6 +18,17 @@ package com.android.systemui.flags
import android.platform.test.flag.junit.SetFlagsRule
+/**
+ * Set the given flag's value to the real value for the current build configuration.
+ * This prevents test code from crashing because it is reading an unspecified flag value.
+ *
+ * REMINDER: You should always test your code with your flag in both configurations, so
+ * generally you should be explicitly enabling or disabling your flag. This method is for
+ * situations where the flag needs to be read (e.g. in the class constructor), but its value
+ * shouldn't affect the actual test cases. In those cases, it's mildly safer to use this method
+ * than to hard-code `false` or `true` because then at least if you're wrong, and the flag value
+ * *does* matter, you'll notice when the flag is flipped and tests start failing.
+ */
fun SetFlagsRule.setFlagDefault(flagName: String) {
if (getFlagDefault(flagName)) {
enableFlags(flagName)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 7a13a0a96ee6..489665cd130a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -106,7 +106,6 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
- whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
// All of these fields are final, so we can't mock them, but are needed so that the surface
// appear amount setter doesn't short circuit.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 819d08a2086e..9bb2434f84ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -43,7 +43,6 @@ import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMET
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -82,6 +81,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.FakeKeyguardStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
@@ -94,6 +94,8 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestDispatcher
@@ -116,8 +118,6 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -195,9 +195,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
powerRepository = FakePowerRepository()
- powerInteractor = PowerInteractorFactory.create(
- repository = powerRepository,
- ).powerInteractor
+ powerInteractor =
+ PowerInteractorFactory.create(
+ repository = powerRepository,
+ )
+ .powerInteractor
val withDeps =
KeyguardInteractorFactory.create(
@@ -210,10 +212,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
keyguardTransitionRepository = FakeKeyguardTransitionRepository()
keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- keyguardInteractor = keyguardInteractor,
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = keyguardTransitionRepository,
+ keyguardInteractor = keyguardInteractor,
)
.keyguardTransitionInteractor
@@ -635,11 +637,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
@Test
fun authenticateDoesNotRunWhenDeviceIsGoingToSleep() =
- testScope.runTest {
- testGatingCheckForFaceAuth {
- powerInteractor.setAsleepForTest()
- }
- }
+ testScope.runTest { testGatingCheckForFaceAuth { powerInteractor.setAsleepForTest() } }
@Test
fun authenticateDoesNotRunWhenSecureCameraIsActive() =
@@ -736,17 +734,21 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
allPreconditionsToRunFaceAuthAreTrue()
Log.i("TEST", "started waking")
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.OFF,
transitionState = TransitionState.FINISHED,
- ))
+ )
+ )
runCurrent()
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
transitionState = TransitionState.STARTED,
- ))
+ )
+ )
runCurrent()
Log.i("TEST", "sending display off")
@@ -766,11 +768,13 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
testScope.runTest {
testGatingCheckForFaceAuth {
powerInteractor.onFinishedWakingUp()
- keyguardTransitionRepository.sendTransitionStep(TransitionStep(
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
transitionState = TransitionState.FINISHED,
- ))
+ )
+ )
runCurrent()
displayRepository.emit(
@@ -919,11 +923,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
@Test
fun detectDoesNotRunWhenDeviceSleepingStartingToSleep() =
- testScope.runTest {
- testGatingCheckForDetect {
- powerInteractor.setAsleepForTest()
- }
- }
+ testScope.runTest { testGatingCheckForDetect { powerInteractor.setAsleepForTest() } }
@Test
fun detectDoesNotRunWhenSecureCameraIsActive() =
@@ -1114,6 +1114,32 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
faceAuthenticateIsCalled()
}
+ @Test
+ fun authFailedCallAfterAuthLockedOutErrorShouldBeIgnored() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+ runCurrent()
+ assertThat(canFaceAuthRun()).isTrue()
+
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false)
+ runCurrent()
+
+ faceAuthenticateIsCalled()
+ authenticationCallback.value.onAuthenticationError(
+ FACE_ERROR_LOCKOUT_PERMANENT,
+ "Too many attempts, face not available"
+ )
+
+ val lockoutError = authStatus() as ErrorFaceAuthenticationStatus
+ assertThat(lockedOut()).isTrue()
+ assertThat(lockoutError.isLockoutError()).isTrue()
+
+ authenticationCallback.value.onAuthenticationFailed()
+ runCurrent()
+
+ assertThat(authStatus()).isEqualTo(lockoutError)
+ }
private suspend fun TestScope.testGatingCheckForFaceAuth(
gatingCheckModifier: suspend () -> Unit
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 5afc4059357c..ad80a06e3fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
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.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
@@ -86,7 +87,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
@Test
- fun startingSecondTransitionWillCancelTheFirstTransition() =
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseLastValue() =
TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
@@ -100,12 +101,19 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
runner.startTransition(
this,
- TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.LAST_VALUE
+ ),
)
val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+ // Second transition starts from .1 (LAST_VALUE)
val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
@@ -114,6 +122,76 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
}
@Test
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReset() =
+ TestScope().runTest {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 3,
+ )
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.RESET
+ ),
+ )
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ // Second transition starts from 0 (RESET)
+ val secondTransitionSteps = listWithStep(start = BigDecimal(0), step = BigDecimal(.1))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ }
+
+ @Test
+ fun startingSecondTransitionWillCancelTheFirstTransitionAndUseReverse() =
+ TestScope().runTest {
+ val steps = mutableListOf<TransitionStep>()
+ val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
+ maxFrames = 3,
+ )
+
+ // Now start 2nd transition, which will interrupt the first
+ val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
+ runner.startTransition(
+ this,
+ TransitionInfo(
+ OWNER_NAME,
+ LOCKSCREEN,
+ AOD,
+ getAnimator(),
+ TransitionModeOnCanceled.REVERSE
+ ),
+ )
+
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
+ assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+
+ // Second transition starts from .9 (REVERSE)
+ val secondTransitionSteps = listWithStep(start = BigDecimal(0.9), step = BigDecimal(.1))
+ assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+
+ job.cancel()
+ job2.cancel()
+ }
+
+ @Test
fun nullAnimatorEnablesManualControlWithUpdateTransition() =
TestScope().runTest {
val steps = mutableListOf<TransitionStep>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index f3930a37c486..275ac804b321 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -57,7 +57,6 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
-import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
@@ -244,7 +243,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -271,7 +270,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -298,7 +297,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -328,7 +327,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -359,7 +358,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -386,7 +385,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -413,7 +412,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -446,7 +445,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to Lockscreen should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -474,7 +473,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to Gone should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -504,7 +503,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -537,7 +536,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -569,7 +568,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDreamingLockscreenHostedTransitionInteractor")
@@ -593,7 +592,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -625,7 +624,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
advanceUntilIdle()
// THEN the transition is ignored
- verify(transitionRepository, never()).startTransition(any(), anyBoolean())
+ verify(transitionRepository, never()).startTransition(any())
coroutineContext.cancelChildren()
}
@@ -642,7 +641,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -669,7 +668,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -696,7 +695,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -719,7 +718,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -749,7 +748,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -780,7 +779,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -806,7 +805,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -838,7 +837,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -871,7 +870,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -901,7 +900,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -929,7 +928,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -957,7 +956,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -981,7 +980,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1011,7 +1010,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition back to DREAMING_LOCKSCREEN_HOSTED should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1042,7 +1041,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to GONE should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1071,7 +1070,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1096,7 +1095,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AlternateBouncer should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1121,7 +1120,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to AlternateBouncer should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -1147,7 +1146,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -1172,7 +1171,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -1199,7 +1198,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
@@ -1223,7 +1222,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -1253,7 +1252,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
// THEN a transition to OCCLUDED should occur
assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
@@ -1287,7 +1286,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// THEN a transition from LOCKSCREEN => OCCLUDED should occur
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1318,7 +1317,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
val info =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
@@ -1339,7 +1338,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur
val info2 =
withArgCaptor<TransitionInfo> {
- verify(transitionRepository).startTransition(capture(), anyBoolean())
+ verify(transitionRepository).startTransition(capture())
}
assertThat(info2.from).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
assertThat(info2.to).isEqualTo(KeyguardState.LOCKSCREEN)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
new file mode 100644
index 000000000000..1ff46db99624
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.MutableStateFlow
+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.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BouncerToGoneFlowsTest : SysuiTestCase() {
+ private lateinit var underTest: BouncerToGoneFlows
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ private lateinit var featureFlags: FakeFeatureFlags
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock
+ private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
+
+ private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansionStateFlow)
+
+ repository = FakeKeyguardTransitionRepository()
+ val featureFlags =
+ FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
+ val interactor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScope().backgroundScope,
+ repository = repository,
+ )
+ .keyguardTransitionInteractor
+ underTest =
+ BouncerToGoneFlows(
+ interactor,
+ statusBarStateController,
+ primaryBouncerInteractor,
+ keyguardDismissActionInteractor,
+ featureFlags,
+ shadeInteractor,
+ )
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+ }
+
+ @Test
+ fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+ shadeExpansionStateFlow.value = 1f
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+ values.forEach { assertThat(it.notificationsAlpha).isIn(Range.closed(0f, 1f)) }
+ }
+
+ @Test
+ fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+ shadeExpansionStateFlow.value = 0f
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
+ }
+
+ @Test
+ fun scrimBehindAlpha_leaveShadeOpen() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach {
+ assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
+ }
+ }
+
+ @Test
+ fun scrimBehindAlpha_doNotLeaveShadeOpen() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER))
+
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+ assertThat(values[3].behindAlpha).isEqualTo(0f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = value,
+ transitionState = state,
+ ownerName = "PrimaryBouncerToGoneTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index d7802aabb298..6cab023d59b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -35,6 +34,7 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -52,12 +52,16 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
private lateinit var featureFlags: FakeFeatureFlags
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+ @Mock private lateinit var bouncerToGoneFlows: BouncerToGoneFlows
@Mock
private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
+ private val shadeExpansionStateFlow = MutableStateFlow(0.1f)
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+
repository = FakeKeyguardTransitionRepository()
val featureFlags =
FakeFeatureFlags().apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) }
@@ -74,6 +78,7 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
primaryBouncerInteractor,
keyguardDismissActionInteractor,
featureFlags,
+ bouncerToGoneFlows,
)
whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
@@ -148,59 +153,6 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
values.forEach { assertThat(it).isEqualTo(1f) }
}
- @Test
- fun scrimAlpha_runDimissFromKeyguard() =
- runTest(UnconfinedTestDispatcher()) {
- val values by collectValues(underTest.scrimAlpha)
-
- whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
-
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0.3f))
- repository.sendTransitionStep(step(0.6f))
- repository.sendTransitionStep(step(1f))
-
- assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isEqualTo(ScrimAlpha()) }
- }
-
- @Test
- fun scrimBehindAlpha_leaveShadeOpen() =
- runTest(UnconfinedTestDispatcher()) {
- val values by collectValues(underTest.scrimAlpha)
-
- whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
-
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0.3f))
- repository.sendTransitionStep(step(0.6f))
- repository.sendTransitionStep(step(1f))
-
- assertThat(values.size).isEqualTo(4)
- values.forEach {
- assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
- }
- }
-
- @Test
- fun scrimBehindAlpha_doNotLeaveShadeOpen() =
- runTest(UnconfinedTestDispatcher()) {
- val values by collectValues(underTest.scrimAlpha)
-
- whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
-
- repository.sendTransitionStep(step(0f, TransitionState.STARTED))
- repository.sendTransitionStep(step(0.3f))
- repository.sendTransitionStep(step(0.6f))
- repository.sendTransitionStep(step(1f))
-
- assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
- values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
- values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
- assertThat(values[3].behindAlpha).isEqualTo(0f)
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 54fc4938e1b0..1abb441439fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -42,7 +42,7 @@ class KeyguardTransitionRunner(
private var frameCount = 1L
private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
private var job: Job? = null
- private var isTerminated = false
+ @Volatile private var isTerminated = false
/**
* For transitions being directed by an animator. Will control the number of frames being
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
new file mode 100644
index 000000000000..fd1e2c7b19a5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
@@ -0,0 +1,84 @@
+package com.android.systemui.mediaprojection
+
+import android.media.projection.IMediaProjectionManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class MediaProjectionMetricsLoggerTest : SysuiTestCase() {
+
+ private val service = mock<IMediaProjectionManager>()
+ private val logger = MediaProjectionMetricsLogger(service)
+
+ @Test
+ fun notifyProjectionInitiated_sourceApp_forwardsToServiceWithMetricsValue() {
+ val hostUid = 123
+ val sessionCreationSource = SessionCreationSource.APP
+
+ logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+ verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_APP)
+ }
+
+ @Test
+ fun notifyProjectionInitiated_sourceCast_forwardsToServiceWithMetricsValue() {
+ val hostUid = 123
+ val sessionCreationSource = SessionCreationSource.CAST
+
+ logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+ verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_CAST)
+ }
+
+ @Test
+ fun notifyProjectionInitiated_sourceSysUI_forwardsToServiceWithMetricsValue() {
+ val hostUid = 123
+ val sessionCreationSource = SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+
+ logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+ verify(service)
+ .notifyPermissionRequestInitiated(
+ hostUid,
+ METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+ )
+ }
+
+ @Test
+ fun notifyProjectionInitiated_sourceUnknown_forwardsToServiceWithMetricsValue() {
+ val hostUid = 123
+ val sessionCreationSource = SessionCreationSource.UNKNOWN
+
+ logger.notifyProjectionInitiated(hostUid, sessionCreationSource)
+
+ verify(service).notifyPermissionRequestInitiated(hostUid, METRICS_CREATION_SOURCE_UNKNOWN)
+ }
+
+ @Test
+ fun notifyPermissionRequestDisplayed_forwardsToService() {
+ val hostUid = 987
+
+ logger.notifyPermissionRequestDisplayed(hostUid)
+
+ verify(service).notifyPermissionRequestDisplayed(hostUid)
+ }
+
+ @Test
+ fun notifyAppSelectorDisplayed_forwardsToService() {
+ val hostUid = 654
+
+ logger.notifyAppSelectorDisplayed(hostUid)
+
+ verify(service).notifyAppSelectorDisplayed(hostUid)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 6cdf4efd67da..5255f71b9c09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -44,7 +44,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
private val thumbnailLoader = FakeThumbnailLoader()
- private fun createController(isFirstStart: Boolean = true) =
+ private fun createController(isFirstStart: Boolean = true, hostUid: Int = 123) =
MediaProjectionAppSelectorController(
taskListProvider,
view,
@@ -55,7 +55,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
callerPackageName,
thumbnailLoader,
isFirstStart,
- logger
+ logger,
+ hostUid,
)
@Before
@@ -212,20 +213,22 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
@Test
fun init_firstStart_logsAppSelectorDisplayed() {
- val controller = createController(isFirstStart = true)
+ val hostUid = 123456789
+ val controller = createController(isFirstStart = true, hostUid)
controller.init()
- verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ verify(logger).notifyAppSelectorDisplayed(hostUid)
}
@Test
fun init_notFirstStart_doesNotLogAppSelectorDisplayed() {
- val controller = createController(isFirstStart = false)
+ val hostUid = 123456789
+ val controller = createController(isFirstStart = false, hostUid)
controller.init()
- verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+ verify(logger, never()).notifyAppSelectorDisplayed(hostUid)
}
private fun givenCaptureAllowed(isAllow: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index 43cf1b5ecc40..ae47a7bfa63d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -37,7 +37,6 @@ import android.testing.TestableLooper
import android.view.IWindowManager
import android.view.View
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.view.LaunchableFrameLayout
@@ -48,6 +47,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.res.R
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -343,8 +343,7 @@ class CustomTileTest : SysuiTestCase() {
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -357,8 +356,7 @@ class CustomTileTest : SysuiTestCase() {
testableLooper.processAllMessages()
verify(activityStarter, never())
- .startPendingIntentDismissingKeyguard(
- any(), any(), any(ActivityLaunchAnimator.Controller::class.java))
+ .startPendingIntentMaybeDismissingKeyguard(any(), nullable(), nullable())
}
@Test
@@ -373,7 +371,7 @@ class CustomTileTest : SysuiTestCase() {
testableLooper.processAllMessages()
verify(activityStarter)
- .startPendingIntentDismissingKeyguard(
+ .startPendingIntentMaybeDismissingKeyguard(
eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
index 0552ced1d678..0e4b113f57ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -124,11 +124,44 @@ class CameraToggleTileTest : SysuiTestCase() {
}
@Test
- fun testLongClickIntent() {
+ fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ val cameraTile = CameraToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(cameraTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ cameraTile.destroy()
+ testableLooper.processAllMessages()
+ }
+ @Test
+ fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
+ val cameraTile = CameraToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ cameraTile.destroy()
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
index 0fcfdb6f318f..b98a7570bb6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -123,11 +123,44 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
}
@Test
- fun testLongClickIntent() {
+ fun testLongClickIntent_safetyCenterEnabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ val micTile = MicrophoneToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS)
+ micTile.destroy()
+ testableLooper.processAllMessages()
+ }
+ @Test
+ fun testLongClickIntent_safetyCenterDisabled() {
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(false)
- assertThat(tile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ val micTile = MicrophoneToggleTile(
+ host,
+ uiEventLogger,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController,
+ safetyCenterManager)
+ assertThat(micTile.longClickIntent?.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS)
+ micTile.destroy()
+ testableLooper.processAllMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index 5b3d45bb6625..c6d156f51905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -22,11 +22,13 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Dialog;
import android.os.Handler;
import android.service.quicksettings.Tile;
import android.testing.AndroidTestingRunner;
@@ -35,11 +37,11 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -48,7 +50,9 @@ import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.res.R;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -89,6 +93,12 @@ public class ScreenRecordTileTest extends SysuiTestCase {
private PanelInteractor mPanelInteractor;
@Mock
private QsEventLogger mUiEventLogger;
+ @Mock
+ private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private Dialog mPermissionDialogPrompt;
+ @Mock
+ private UserContextProvider mUserContextProvider;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@@ -99,6 +109,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
+ when(mUserContextProvider.getUserContext()).thenReturn(mContext);
when(mHost.getContext()).thenReturn(mContext);
mTile = new ScreenRecordTile(
@@ -116,7 +127,9 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mKeyguardDismissUtil,
mKeyguardStateController,
mDialogLaunchAnimator,
- mPanelInteractor
+ mPanelInteractor,
+ mMediaProjectionMetricsLogger,
+ mUserContextProvider
);
mTile.initialize();
@@ -280,4 +293,28 @@ public class ScreenRecordTileTest extends SysuiTestCase {
assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_screen_record_icon_off));
}
+ @Test
+ public void showingDialogPrompt_logsMediaProjectionPermissionRequested() {
+ when(mController.isStarting()).thenReturn(false);
+ when(mController.isRecording()).thenReturn(false);
+ when(mController.createScreenRecordDialog(any(), any(), any(), any(), any()))
+ .thenReturn(mPermissionDialogPrompt);
+
+ mTile.handleClick(null /* view */);
+ mTestableLooper.processAllMessages();
+
+ verify(mController).createScreenRecordDialog(any(), eq(mFeatureFlags),
+ eq(mDialogLaunchAnimator), eq(mActivityStarter), any());
+ var onDismissAction = ArgumentCaptor.forClass(ActivityStarter.OnDismissAction.class);
+ verify(mKeyguardDismissUtil).executeWhenUnlocked(
+ onDismissAction.capture(), anyBoolean(), anyBoolean());
+ assertNotNull(onDismissAction.getValue());
+
+ onDismissAction.getValue().onDismiss();
+
+ verify(mPermissionDialogPrompt).show();
+ verify(mMediaProjectionMetricsLogger)
+ .notifyPermissionRequestDisplayed(mContext.getUserId());
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
index 5659f0173860..95ee3b7a8495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt
@@ -32,16 +32,16 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
-class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+class QSTileIntentUserInputHandlerTest : SysuiTestCase() {
@Mock private lateinit var activityStarted: ActivityStarter
- lateinit var underTest: QSTileIntentUserActionHandler
+ lateinit var underTest: QSTileIntentUserInputHandler
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = QSTileIntentUserActionHandler(activityStarted)
+ underTest = QSTileIntentUserInputHandler(activityStarted)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index f1fcee318141..31d02ed78404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -23,11 +23,13 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dump.LogcatEchoTrackerAlways
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
@@ -42,6 +44,7 @@ import org.mockito.MockitoAnnotations
class QSTileLoggerTest : SysuiTestCase() {
@Mock private lateinit var statusBarController: StatusBarStateController
+ @Mock private lateinit var logBufferFactory: LogBufferFactory
private val chattyLogBuffer = LogBuffer("TestChatty", 5, LogcatEchoTrackerAlways())
private val logBuffer = LogBuffer("Test", 1, LogcatEchoTrackerAlways())
@@ -51,10 +54,11 @@ class QSTileLoggerTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ whenever(logBufferFactory.create(any(), any(), any())).thenReturn(logBuffer)
underTest =
QSTileLogger(
mapOf(TileSpec.create("chatty_tile") to chattyLogBuffer),
- { logBuffer },
+ logBufferFactory,
statusBarController
)
}
@@ -139,7 +143,6 @@ class QSTileLoggerTest : SysuiTestCase() {
fun testLogStateUpdate() {
underTest.logStateUpdate(
TileSpec.create("test_spec"),
- StateUpdateTrigger.ForceUpdate,
QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
"test_data",
)
@@ -147,21 +150,36 @@ class QSTileLoggerTest : SysuiTestCase() {
assertThat(logBuffer.getStringBuffer())
.contains(
"tile state update: " +
- "trigger=force, " +
- "state=[" +
- "label=, " +
+ "state=[label=, " +
"state=INACTIVE, " +
"s_label=null, " +
"cd=null, " +
"sd=null, " +
"svi=None, " +
"enabled=ENABLED, " +
- "a11y=null" +
- "], " +
+ "a11y=null], " +
"data=test_data"
)
}
+ @Test
+ fun testLogForceUpdate() {
+ underTest.logForceUpdate(
+ TileSpec.create("test_spec"),
+ )
+
+ assertThat(logBuffer.getStringBuffer()).contains("tile data force update")
+ }
+
+ @Test
+ fun testLogInitialUpdate() {
+ underTest.logInitialRequest(
+ TileSpec.create("test_spec"),
+ )
+
+ assertThat(logBuffer.getStringBuffer()).contains("tile data initial update")
+ }
+
private fun LogBuffer.getStringBuffer(): String {
val stringWriter = StringWriter()
dump(PrintWriter(stringWriter), 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
index 2084aeb7fe83..9b85012b29a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -29,11 +29,11 @@ import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -55,6 +55,7 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
@Mock private lateinit var qsTileLogger: QSTileLogger
@Mock private lateinit var qsTileAnalytics: QSTileAnalytics
+ private val fakeUserRepository = FakeUserRepository()
private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor()
@@ -86,7 +87,7 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
assertThat(fakeQSTileDataInteractor.dataRequests.first())
- .isEqualTo(QSTileDataRequest(1, StateUpdateTrigger.InitialRequest))
+ .isEqualTo(FakeQSTileDataInteractor.DataRequest(1))
}
private fun createViewModel(
@@ -102,9 +103,11 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {}
},
fakeDisabledByPolicyInteractor,
+ fakeUserRepository,
fakeFalsingManager,
qsTileAnalytics,
qsTileLogger,
+ FakeSystemClock(),
testCoroutineDispatcher,
scope.backgroundScope,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index e1345d28dbd0..c1f2d0cc518f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -22,11 +22,11 @@ import android.os.PowerManager
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.Flags
import com.android.systemui.model.SysUiState
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -45,7 +45,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Assume.assumeTrue
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
@@ -87,6 +87,11 @@ class SceneContainerStartableTest : SysuiTestCase() {
powerInteractor = powerInteractor,
)
+ @Before
+ fun setUp() {
+ mSetFlagsRule.enableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
+ }
+
@Test
fun hydrateVisibility() =
testScope.runTest {
@@ -520,7 +525,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
authenticationMethod: AuthenticationMethodModel? = null,
startsAwake: Boolean = true,
): MutableStateFlow<ObservableTransitionState> {
- assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
sceneContainerFlags.enabled = true
utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
index f6195aa9c3f7..0bed4d0d376a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/shared/flag/SceneContainerFlagsTest.kt
@@ -16,7 +16,10 @@
package com.android.systemui.scene.shared.flag
+import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.filters.SmallTest
+import com.android.systemui.FakeFeatureFlagsImpl
+import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -24,8 +27,8 @@ import com.android.systemui.flags.ReleasedFlag
import com.android.systemui.flags.ResourceBooleanFlag
import com.android.systemui.flags.UnreleasedFlag
import com.google.common.truth.Truth.assertThat
-import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -36,27 +39,50 @@ internal class SceneContainerFlagsTest(
private val testCase: TestCase,
) : SysuiTestCase() {
+ @Rule @JvmField val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
private lateinit var underTest: SceneContainerFlags
@Before
fun setUp() {
+ // TODO(b/283300105): remove this reflection setting once the hard-coded
+ // Flags.SCENE_CONTAINER_ENABLED is no longer needed.
+ val field = Flags::class.java.getField("SCENE_CONTAINER_ENABLED")
+ field.isAccessible = true
+ field.set(null, true)
+
val featureFlags =
FakeFeatureFlagsClassic().apply {
- SceneContainerFlagsImpl.flags.forEach { flag ->
- when (flag) {
- is ResourceBooleanFlag -> set(flag, testCase.areAllFlagsSet)
- is ReleasedFlag -> set(flag, testCase.areAllFlagsSet)
- is UnreleasedFlag -> set(flag, testCase.areAllFlagsSet)
- else -> error("Unsupported flag type ${flag.javaClass}")
+ SceneContainerFlagsImpl.classicFlagTokens.forEach { flagToken ->
+ when (flagToken) {
+ is ResourceBooleanFlag -> set(flagToken, testCase.areAllFlagsSet)
+ is ReleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
+ is UnreleasedFlag -> set(flagToken, testCase.areAllFlagsSet)
+ else -> error("Unsupported flag type ${flagToken.javaClass}")
}
}
}
- underTest = SceneContainerFlagsImpl(featureFlags, testCase.isComposeAvailable)
+ // TODO(b/306421592): get the aconfig FeatureFlags from the SetFlagsRule.
+ val aconfigFlags = FakeFeatureFlagsImpl()
+
+ listOf(
+ AconfigFlags.FLAG_SCENE_CONTAINER,
+ )
+ .forEach { flagToken ->
+ setFlagsRule.enableFlags(flagToken)
+ aconfigFlags.setFlag(flagToken, testCase.areAllFlagsSet)
+ }
+
+ underTest =
+ SceneContainerFlagsImpl(
+ featureFlagsClassic = featureFlags,
+ featureFlags = aconfigFlags,
+ isComposeAvailable = testCase.isComposeAvailable,
+ )
}
@Test
fun isEnabled() {
- assumeTrue(Flags.SCENE_CONTAINER_ENABLED)
assertThat(underTest.isEnabled()).isEqualTo(testCase.expectedEnabled)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 90d2e78a411f..c439cfe6270f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -23,11 +23,13 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Dialog;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import android.testing.AndroidTestingRunner;
@@ -64,6 +66,8 @@ import org.mockito.MockitoAnnotations;
*/
public class RecordingControllerTest extends SysuiTestCase {
+ private static final int TEST_USER_ID = 12345;
+
private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@Mock
@@ -91,6 +95,11 @@ public class RecordingControllerTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ Context spiedContext = spy(mContext);
+ when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
+
+ when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
+
mFeatureFlags = new FakeFeatureFlags();
mController = new RecordingController(
mMainExecutor,
@@ -288,7 +297,6 @@ public class RecordingControllerTest extends SysuiTestCase {
if (Looper.myLooper() == null) {
Looper.prepare();
}
-
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
@@ -297,6 +305,8 @@ public class RecordingControllerTest extends SysuiTestCase {
mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
verify(mMediaProjectionMetricsLogger)
- .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+ .notifyProjectionInitiated(
+ TEST_USER_ID,
+ SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index da4dc850fcae..bf12d7deb076 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.screenrecord
+import android.content.Intent
import android.os.UserHandle
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -25,11 +26,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.permission.ENTIRE_SCREEN
import com.android.systemui.mediaprojection.permission.SINGLE_APP
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
@@ -38,6 +41,9 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -62,6 +68,7 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
ScreenRecordPermissionDialog(
context,
UserHandle.of(0),
+ TEST_HOST_UID,
controller,
starter,
userContextProvider,
@@ -105,6 +112,19 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
}
@Test
+ fun startClicked_singleAppSelected_passesHostUidToAppSelector() {
+ dialog.show()
+ onSpinnerItemSelected(SINGLE_APP)
+
+ clickOnStart()
+
+ assertExtraPassedToAppSelector(
+ extraKey = MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID,
+ value = TEST_HOST_UID
+ )
+ }
+
+ @Test
fun showDialog_dialogIsShowing() {
dialog.show()
@@ -133,9 +153,25 @@ class ScreenRecordPermissionDialogTest : SysuiTestCase() {
dialog.requireViewById<View>(android.R.id.button2).performClick()
}
+ private fun clickOnStart() {
+ dialog.requireViewById<View>(android.R.id.button1).performClick()
+ }
+
private fun onSpinnerItemSelected(position: Int) {
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
checkNotNull(spinner.onItemSelectedListener)
.onItemSelected(spinner, mock(), position, /* id= */ 0)
}
+
+ private fun assertExtraPassedToAppSelector(extraKey: String, value: Int) {
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(starter).startActivity(intentCaptor.capture(), /* dismissShade= */ eq(true))
+
+ val intent = intentCaptor.value
+ assertThat(intent.extras!!.getInt(extraKey)).isEqualTo(value)
+ }
+
+ companion object {
+ private const val TEST_HOST_UID = 12345
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 4f3216da7370..4e3e165c83bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.KeyEvent
import android.view.MotionEvent
+import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardMessageAreaController
@@ -43,11 +44,20 @@ import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags.ALTERNATE_BOUNCER_VIEW
+import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED
+import com.android.systemui.flags.Flags.MIGRATE_NSSL
+import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
+import com.android.systemui.flags.Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON
+import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
import com.android.systemui.flags.SystemPropertiesHelper
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.DismissCallbackRegistry
@@ -69,7 +79,8 @@ import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
@@ -83,6 +94,7 @@ import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -97,6 +109,7 @@ import org.mockito.Mock
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.Optional
@@ -135,6 +148,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+ private lateinit var mCommunalRepository: FakeCommunalRepository
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@@ -149,7 +164,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- private val notificationExpansionRepository = NotificationExpansionRepository()
+ private val notificationLaunchAnimationRepository = NotificationLaunchAnimationRepository()
+ private val notificationLaunchAnimationInteractor =
+ NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
private lateinit var fakeClock: FakeSystemClock
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
@@ -159,7 +176,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
private lateinit var testScope: TestScope
- private lateinit var featureFlags: FakeFeatureFlags
+ private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
@Before
fun setUp() {
@@ -174,14 +191,16 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow<TransitionStep>())
- featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
- featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
- featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
- featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
- featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
- featureFlags.set(Flags.MIGRATE_NSSL, false)
- featureFlags.set(Flags.ALTERNATE_BOUNCER_VIEW, false)
+ featureFlagsClassic = FakeFeatureFlagsClassic()
+ featureFlagsClassic.set(TRACKPAD_GESTURE_COMMON, true)
+ featureFlagsClassic.set(TRACKPAD_GESTURE_FEATURES, false)
+ featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
+ featureFlagsClassic.set(REVAMPED_BOUNCER_MESSAGES, true)
+ featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+ featureFlagsClassic.set(MIGRATE_NSSL, false)
+ featureFlagsClassic.set(ALTERNATE_BOUNCER_VIEW, false)
+
+ mCommunalRepository = FakeCommunalRepository()
testScope = TestScope()
fakeClock = FakeSystemClock()
@@ -216,14 +235,16 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
- notificationExpansionRepository,
- featureFlags,
+ mCommunalViewModel,
+ mCommunalRepository,
+ notificationLaunchAnimationInteractor,
+ featureFlagsClassic,
fakeClock,
BouncerMessageInteractor(
repository = BouncerMessageRepositoryImpl(),
userRepository = FakeUserRepository(),
countDownTimerUtil = mock(CountDownTimerUtil::class.java),
- featureFlags = featureFlags,
+ featureFlags = featureFlagsClassic,
updateMonitor = mock(KeyguardUpdateMonitor::class.java),
biometricSettingsRepository = FakeBiometricSettingsRepository(),
applicationScope = testScope.backgroundScope,
@@ -443,7 +464,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
.thenReturn(true)
- featureFlags.set(Flags.MIGRATE_NSSL, true)
+ featureFlagsClassic.set(MIGRATE_NSSL, true)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse()
@@ -460,7 +481,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT))
.thenReturn(false)
- featureFlags.set(Flags.MIGRATE_NSSL, true)
+ featureFlagsClassic.set(MIGRATE_NSSL, true)
// THEN touch should NOT be intercepted by NotificationShade
assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue()
@@ -474,6 +495,48 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
}
@Test
+ fun setsUpCommunalHubLayout_whenFlagEnabled() {
+ if (!isComposeAvailable()) {
+ return
+ }
+
+ mCommunalRepository.setIsCommunalEnabled(true)
+
+ val mockCommunalPlaceholder = mock(View::class.java)
+ val fakeViewIndex = 20
+ whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+ whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+ whenever(view.context).thenReturn(context)
+
+ underTest.setupCommunalHubLayout()
+
+ // Communal view added as a child of the container at the proper index, the stub is removed.
+ verify(view).removeView(mockCommunalPlaceholder)
+ verify(view).addView(any(), eq(fakeViewIndex))
+ }
+
+ @Test
+ fun doesNotSetupCommunalHubLayout_whenFlagDisabled() {
+ if (!isComposeAvailable()) {
+ return
+ }
+
+ mCommunalRepository.setIsCommunalEnabled(false)
+
+ val mockCommunalPlaceholder = mock(View::class.java)
+ val fakeViewIndex = 20
+ whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder)
+ whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex)
+ whenever(view.context).thenReturn(context)
+
+ underTest.setupCommunalHubLayout()
+
+ // No adding or removing of views occurs.
+ verify(view, times(0)).removeView(mockCommunalPlaceholder)
+ verify(view, times(0)).addView(any(), eq(fakeViewIndex))
+ }
+
+ @Test
fun forwardsDispatchKeyEvent() {
val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_B)
interactionEventHandler.dispatchKeyEvent(keyEvent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 4d3eab45d001..3d5d26ab194e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -42,6 +42,8 @@ import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.dump.logcatLogBuffer
@@ -67,7 +69,8 @@ import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -142,6 +145,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
private lateinit var unfoldTransitionProgressProvider:
Optional<UnfoldTransitionProgressProvider>
@Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var mCommunalViewModel: CommunalViewModel
+ private lateinit var mCommunalRepository: FakeCommunalRepository
@Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@@ -178,6 +183,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
.thenReturn(emptyFlow())
+ mCommunalRepository = FakeCommunalRepository()
+
val featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
@@ -218,7 +225,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
Mockito.mock(KeyguardMessageAreaController.Factory::class.java),
keyguardTransitionInteractor,
primaryBouncerToGoneTransitionViewModel,
- NotificationExpansionRepository(),
+ mCommunalViewModel,
+ mCommunalRepository,
+ NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository()),
featureFlags,
FakeSystemClock(),
BouncerMessageInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 81382a44def6..3a260ae374c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -445,105 +445,6 @@ class ShadeInteractorTest : SysuiTestCase() {
}
@Test
- fun expanding_shadeDraggedDown_expandingTrue() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade and QS collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade partially expanded
- shadeRepository.setLegacyShadeExpansion(.5f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
- }
-
- @Test
- fun expanding_qsDraggedDown_expandingTrue() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade and QS collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade partially expanded
- shadeRepository.setQsExpansion(.5f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
- }
-
- @Test
- fun expanding_shadeDraggedUpAndDown() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // WHEN shade starts collapsed then partially expanded
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setLegacyShadeExpansion(.5f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // THEN anyExpanding is true
- assertThat(actual).isTrue()
-
- // WHEN shade dragged up a bit
- shadeRepository.setLegacyShadeExpansion(.2f)
- runCurrent()
-
- // THEN anyExpanding is still true
- assertThat(actual).isTrue()
-
- // WHEN shade dragged down a bit
- shadeRepository.setLegacyShadeExpansion(.7f)
- runCurrent()
-
- // THEN anyExpanding is still true
- assertThat(actual).isTrue()
-
- // WHEN shade fully expanded
- shadeRepository.setLegacyShadeExpansion(1f)
- runCurrent()
-
- // THEN anyExpanding is now false
- assertThat(actual).isFalse()
-
- // WHEN shade dragged up a bit
- shadeRepository.setLegacyShadeExpansion(.7f)
- runCurrent()
-
- // THEN anyExpanding is still false
- assertThat(actual).isFalse()
- }
-
- @Test
- fun expanding_shadeDraggedDownThenUp_expandingFalse() =
- testScope.runTest() {
- val actual by collectLastValue(underTest.isAnyExpanding)
-
- // GIVEN shade starts collapsed
- shadeRepository.setLegacyShadeExpansion(0f)
- shadeRepository.setQsExpansion(0f)
- runCurrent()
-
- // WHEN shade expands but doesn't complete
- shadeRepository.setLegacyShadeExpansion(.5f)
- runCurrent()
- shadeRepository.setLegacyShadeExpansion(0f)
- runCurrent()
-
- // THEN anyExpanding is false
- assertThat(actual).isFalse()
- }
-
- @Test
fun lockscreenShadeExpansion_idle_onScene() =
testScope.runTest() {
// GIVEN an expansion flow based on transitions to and from a scene
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index 34126798e87e..2b3fd34cedbf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -37,8 +37,12 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository;
+import com.android.systemui.statusbar.domain.interactor.SilentNotificationStatusIconsVisibilityInteractor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -68,9 +72,14 @@ public class NotificationListenerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
+ FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
+ featureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
mListener = new NotificationListener(
mContext,
+ featureFlags,
mNotificationManager,
+ new SilentNotificationStatusIconsVisibilityInteractor(
+ new NotificationListenerSettingsRepository()),
mFakeSystemClock,
mFakeExecutor,
mPluginManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index a5f5fc7e36f2..43adc69be13f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -22,14 +22,18 @@ import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.UserHandle.USER_ALL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
import static android.provider.Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS;
+import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -99,6 +103,7 @@ import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
+ private static final int TEST_PROFILE_USERHANDLE = 12;
@Mock
private NotificationPresenter mPresenter;
@Mock
@@ -701,6 +706,60 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
assertFalse(mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(newUserId));
}
+ @Test
+ public void testProfileAvailabilityIntent() {
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mLockscreenUserManager.mCurrentProfiles.clear();
+ assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+ simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_AVAILABLE);
+ assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+ }
+
+ @Test
+ public void testProfileUnAvailabilityIntent() {
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mLockscreenUserManager.mCurrentProfiles.clear();
+ assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+ simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+ assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+ }
+
+ @Test
+ public void testManagedProfileAvailabilityIntent() {
+ mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mLockscreenUserManager.mCurrentProfiles.clear();
+ mLockscreenUserManager.mCurrentManagedProfiles.clear();
+ assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+ assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+ simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+ assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+ assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
+ }
+
+ @Test
+ public void testManagedProfileUnAvailabilityIntent() {
+ mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mLockscreenUserManager.mCurrentProfiles.clear();
+ mLockscreenUserManager.mCurrentManagedProfiles.clear();
+ assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
+ assertEquals(0, mLockscreenUserManager.mCurrentManagedProfiles.size());
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+ simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ assertEquals(2, mLockscreenUserManager.mCurrentProfiles.size());
+ assertEquals(1, mLockscreenUserManager.mCurrentManagedProfiles.size());
+ }
+
+ private void simulateProfileAvailabilityActions(String intentAction) {
+ BroadcastReceiver broadcastReceiver =
+ mLockscreenUserManager.getBaseBroadcastReceiverForTest();
+ final Intent intent = new Intent(intentAction);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
+ broadcastReceiver.onReceive(mContext, intent);
+ }
+
private class TestNotificationLockscreenUserManager
extends NotificationLockscreenUserManagerImpl {
public TestNotificationLockscreenUserManager(Context context) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
deleted file mode 100644
index cfcf4257ce28..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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
-
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
-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.anyBoolean
-import org.mockito.Mockito.doCallRealMethod
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-/**
- * Temporary test for the lock screen live wallpaper project.
- *
- * TODO(b/273443374): remove this test
- */
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class NotificationMediaManagerTest : SysuiTestCase() {
-
- @Mock private lateinit var notificationMediaManager: NotificationMediaManager
-
- @Mock private lateinit var mockBackDropView: BackDropView
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean())
- doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView
- }
-
- @After fun tearDown() {}
-
- /** Check that updateMediaMetaData is a no-op with mIsLockscreenLiveWallpaperEnabled = true */
- @Test
- fun testUpdateMediaMetaDataDisabled() {
- notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true
- for (metaDataChanged in listOf(true, false)) {
- for (allowEnterAnimation in listOf(true, false)) {
- notificationMediaManager.updateMediaMetaData(metaDataChanged)
- verify(notificationMediaManager, never()).mediaMetadata
- }
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
index 839770267c74..df8afde1b9a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/FakeStatusEvent.kt
@@ -26,6 +26,7 @@ class FakeStatusEvent(
override var forceVisible: Boolean = false,
override val showAnimation: Boolean = true,
override var contentDescription: String? = "",
+ override val shouldAnnounceAccessibilityEvent: Boolean = false
) : StatusEvent
class FakePrivacyStatusEvent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 4fcccf887e58..fee8b82a3038 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -33,6 +33,8 @@ import com.android.systemui.statusbar.BatteryStatusChip
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert.assertEquals
@@ -370,6 +372,63 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
}
@Test
+ fun testAccessibilityAnnouncement_announced() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+ val accessibilityDesc = "Some desc"
+ val mockView = mock<View>()
+ val mockAnimatableView =
+ mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+ scheduleFakeEventWithView(
+ accessibilityDesc,
+ mockAnimatableView,
+ shouldAnnounceAccessibilityEvent = true
+ )
+ fastForwardAnimationToState(ANIMATING_OUT)
+
+ verify(mockView).announceForAccessibility(eq(accessibilityDesc))
+ }
+
+ @Test
+ fun testAccessibilityAnnouncement_nullDesc_noAnnouncement() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+ val accessibilityDesc = null
+ val mockView = mock<View>()
+ val mockAnimatableView =
+ mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+ scheduleFakeEventWithView(
+ accessibilityDesc,
+ mockAnimatableView,
+ shouldAnnounceAccessibilityEvent = true
+ )
+ fastForwardAnimationToState(ANIMATING_OUT)
+
+ verify(mockView, never()).announceForAccessibility(any())
+ }
+
+ @Test
+ fun testAccessibilityAnnouncement_notNeeded_noAnnouncement() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+ val accessibilityDesc = "something"
+ val mockView = mock<View>()
+ val mockAnimatableView =
+ mock<BackgroundAnimatableView> { whenever(view).thenReturn(mockView) }
+
+ scheduleFakeEventWithView(
+ accessibilityDesc,
+ mockAnimatableView,
+ shouldAnnounceAccessibilityEvent = false
+ )
+ fastForwardAnimationToState(ANIMATING_OUT)
+
+ verify(mockView, never()).announceForAccessibility(any())
+ }
+
+ @Test
fun testPrivacyDot_isRemovedDuringChipAnimation() = runTest {
// Instantiate class under test with TestScope from runTest
initializeSystemStatusAnimationScheduler(testScope = this)
@@ -572,6 +631,20 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
return privacyChip
}
+ private fun scheduleFakeEventWithView(
+ desc: String?,
+ view: BackgroundAnimatableView,
+ shouldAnnounceAccessibilityEvent: Boolean
+ ) {
+ val fakeEvent =
+ FakeStatusEvent(
+ viewCreator = { view },
+ contentDescription = desc,
+ shouldAnnounceAccessibilityEvent = shouldAnnounceAccessibilityEvent
+ )
+ systemStatusAnimationScheduler.onStatusEvent(fakeEvent)
+ }
+
private fun createAndScheduleFakeBatteryEvent(): BatteryStatusChip {
val batteryChip = BatteryStatusChip(mContext)
val fakeBatteryEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 235ac5c2e9cc..605936372e7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -11,7 +11,8 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -44,7 +45,8 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
private lateinit var notificationTestHelper: NotificationTestHelper
private lateinit var notification: ExpandableNotificationRow
private lateinit var controller: NotificationLaunchAnimatorController
- private val notificationExpansionRepository = NotificationExpansionRepository()
+ private val notificationLaunchAnimationInteractor =
+ NotificationLaunchAnimationInteractor(NotificationLaunchAnimationRepository())
private val testScope = TestScope()
@@ -57,16 +59,17 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
fun setUp() {
allowTestableLooperAsMainThread()
notificationTestHelper =
- NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
notification = notificationTestHelper.createRow()
- controller = NotificationLaunchAnimatorController(
- notificationExpansionRepository,
+ controller =
+ NotificationLaunchAnimatorController(
+ notificationLaunchAnimationInteractor,
notificationListContainer,
headsUpManager,
notification,
jankMonitor,
onFinishAnimationCallback
- )
+ )
}
private fun flagNotificationAsHun() {
@@ -80,13 +83,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertFalse(isExpandAnimationRunning!!)
- verify(headsUpManager).removeNotification(
- notificationKey, true /* releaseImmediately */, true /* animate */)
+ verify(headsUpManager)
+ .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
verify(onFinishAnimationCallback).run()
}
@@ -97,13 +101,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
assertTrue(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertFalse(isExpandAnimationRunning!!)
- verify(headsUpManager).removeNotification(
- notificationKey, true /* releaseImmediately */, true /* animate */)
+ verify(headsUpManager)
+ .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
verify(onFinishAnimationCallback).run()
}
@@ -114,13 +119,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
assertFalse(HeadsUpUtil.isClickedHeadsUpNotification(notification))
assertFalse(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertFalse(isExpandAnimationRunning!!)
- verify(headsUpManager).removeNotification(
- notificationKey, true /* releaseImmediately */, false /* animate */)
+ verify(headsUpManager)
+ .removeNotification(notificationKey, true /* releaseImmediately */, false /* animate */)
verify(onFinishAnimationCallback).run()
}
@@ -128,15 +134,21 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
fun testAlertingSummaryHunRemovedOnNonAlertingChildLaunch() {
val GROUP_KEY = "test_group_key"
- val summary = NotificationEntryBuilder().setGroup(mContext, GROUP_KEY).setId(0).apply {
- modifyNotification(mContext).setSmallIcon(R.drawable.ic_person)
- }.build()
+ val summary =
+ NotificationEntryBuilder()
+ .setGroup(mContext, GROUP_KEY)
+ .setId(0)
+ .apply { modifyNotification(mContext).setSmallIcon(R.drawable.ic_person) }
+ .build()
assertNotSame(summary.key, notification.entry.key)
notificationTestHelper.createRow(summary)
- GroupEntryBuilder().setKey(GROUP_KEY).setSummary(summary).addChild(notification.entry)
- .build()
+ GroupEntryBuilder()
+ .setKey(GROUP_KEY)
+ .setSummary(summary)
+ .addChild(notification.entry)
+ .build()
assertSame(summary, notification.entry.parent?.summary)
`when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
@@ -147,10 +159,14 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
controller.onLaunchAnimationEnd(isExpandingFullyAbove = true)
- verify(headsUpManager).removeNotification(
- summary.key, true /* releaseImmediately */, false /* animate */)
- verify(headsUpManager, never()).removeNotification(
- notification.entry.key, true /* releaseImmediately */, false /* animate */)
+ verify(headsUpManager)
+ .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */)
+ verify(headsUpManager, never())
+ .removeNotification(
+ notification.entry.key,
+ true /* releaseImmediately */,
+ false /* animate */
+ )
}
@Test
@@ -158,9 +174,10 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
controller.onIntentStarted(willAnimate = true)
assertTrue(notification.entry.isExpandAnimationRunning)
- val isExpandAnimationRunning by testScope.collectLastValue(
- notificationExpansionRepository.isExpandAnimationRunning
- )
+ val isExpandAnimationRunning by
+ testScope.collectLastValue(
+ notificationLaunchAnimationInteractor.isLaunchAnimationRunning
+ )
assertTrue(isExpandAnimationRunning!!)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index c664c39f4432..2ef4374ce13a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -18,10 +18,16 @@ package com.android.systemui.statusbar.notification;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.test.suitebuilder.annotation.SmallTest;
@@ -33,8 +39,8 @@ import android.view.View;
import android.view.animation.Interpolator;
import com.android.app.animation.Interpolators;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -85,7 +91,7 @@ public class PropertyAnimatorTest extends SysuiTestCase {
return mEffectiveProperty;
}
};
- private AnimatorListenerAdapter mFinishListener = mock(AnimatorListenerAdapter.class);
+ private AnimatorListenerAdapter mFinishListener;
private AnimationProperties mAnimationProperties = new AnimationProperties() {
@Override
public AnimationFilter getAnimationFilter() {
@@ -104,6 +110,7 @@ public class PropertyAnimatorTest extends SysuiTestCase {
@Before
public void setUp() {
mView = new View(getContext());
+ mFinishListener = mock(AnimatorListenerAdapter.class);
}
@Test
@@ -229,6 +236,32 @@ public class PropertyAnimatorTest extends SysuiTestCase {
}
@Test
+ public void testListenerCallbackOrderAndTagState() {
+ mAnimationFilter.reset();
+ mAnimationFilter.animate(mProperty.getProperty());
+ mAnimationProperties.setCustomInterpolator(mEffectiveProperty, mTestInterpolator);
+ mAnimationProperties.setDuration(500);
+
+ // Validates that the onAnimationEnd function set by PropertyAnimator was run first.
+ doAnswer(invocation -> {
+ assertNull(mView.getTag(mProperty.getAnimatorTag()));
+ return null;
+ })
+ .when(mFinishListener)
+ .onAnimationEnd(any(Animator.class), anyBoolean());
+
+ // Begin the animation and verify it set state correctly
+ PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
+ ValueAnimator animator = ViewState.getChildTag(mView, mProperty.getAnimatorTag());
+ assertNotNull(animator);
+ assertNotNull(mView.getTag(mProperty.getAnimatorTag()));
+
+ // Terminate the animation to run end runners, and validate they executed.
+ animator.end();
+ verify(mFinishListener).onAnimationEnd(animator, false);
+ }
+
+ @Test
public void testIsAnimating() {
mAnimationFilter.reset();
mAnimationFilter.animate(mProperty.getProperty());
@@ -236,4 +269,4 @@ public class PropertyAnimatorTest extends SysuiTestCase {
PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties);
assertTrue(PropertyAnimator.isAnimating(mView, mProperty));
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 546abd4ec79a..6c1f537e754f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -39,10 +39,10 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.mockito.any
@@ -246,7 +246,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
unseenFilter.onCleanup()
// THEN: The SeenNotificationProvider has been updated to reflect the suppression
- assertThat(notificationListInteractor.hasFilteredOutSeenNotifications.value).isTrue()
+ assertThat(seenNotificationsInteractor.hasFilteredOutSeenNotifications.value).isTrue()
}
}
@@ -597,7 +597,8 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
FakeSettings().apply {
putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
}
- val notificationListInteractor = NotificationListInteractor(NotificationListRepository())
+ val seenNotificationsInteractor =
+ SeenNotificationsInteractor(ActiveNotificationListRepository())
val keyguardCoordinator =
KeyguardCoordinator(
testDispatcher,
@@ -610,7 +611,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
fakeSettings,
- notificationListInteractor,
+ seenNotificationsInteractor,
statusBarStateController,
)
keyguardCoordinator.attach(notifPipeline)
@@ -618,7 +619,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
KeyguardCoordinatorTestScope(
keyguardCoordinator,
testScope,
- notificationListInteractor,
+ seenNotificationsInteractor,
fakeSettings,
)
.testBlock()
@@ -628,7 +629,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
private inner class KeyguardCoordinatorTestScope(
private val keyguardCoordinator: KeyguardCoordinator,
private val scope: TestScope,
- val notificationListInteractor: NotificationListInteractor,
+ val seenNotificationsInteractor: SeenNotificationsInteractor,
private val fakeSettings: FakeSettings,
) : CoroutineScope by scope {
val testScheduler: TestCoroutineScheduler
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 655bd7243836..a736182b6329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -19,6 +19,8 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -27,6 +29,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfte
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
import com.android.systemui.statusbar.notification.collection.render.NotifStats
+import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.phone.NotificationIconAreaController
@@ -37,8 +40,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -52,13 +55,23 @@ class StackCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var groupExpansionManagerImpl: GroupExpansionManagerImpl
@Mock private lateinit var notificationIconAreaController: NotificationIconAreaController
+ @Mock private lateinit var renderListInteractor: RenderNotificationListInteractor
@Mock private lateinit var stackController: NotifStackController
@Mock private lateinit var section: NotifSection
+ val featureFlags =
+ FakeFeatureFlagsClassic().apply { setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR) }
+
@Before
fun setUp() {
initMocks(this)
- coordinator = StackCoordinator(groupExpansionManagerImpl, notificationIconAreaController)
+ coordinator =
+ StackCoordinator(
+ featureFlags,
+ groupExpansionManagerImpl,
+ notificationIconAreaController,
+ renderListInteractor,
+ )
coordinator.attach(pipeline)
afterRenderListListener = withArgCaptor {
verify(pipeline).addOnAfterRenderListListener(capture())
@@ -68,11 +81,19 @@ class StackCoordinatorTest : SysuiTestCase() {
@Test
fun testUpdateNotificationIcons() {
+ featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, false)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
}
@Test
+ fun testSetRenderedListOnInteractor() {
+ featureFlags.set(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR, true)
+ afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
+ verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
+ }
+
+ @Test
fun testSetNotificationStats_clearableAlerting() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
new file mode 100644
index 000000000000..683d0aa33d4a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationAlertsInteractorTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.app.StatusBarManager
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
+import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.Test
+
+@SmallTest
+class NotificationAlertsInteractorTest : SysuiTestCase() {
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: NotificationAlertsInteractor
+ val disableFlags: FakeDisableFlagsRepository
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase): TestComponent
+ }
+ }
+
+ private val testComponent: TestComponent =
+ DaggerNotificationAlertsInteractorTest_TestComponent.factory().create(test = this)
+
+ @Test
+ fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() =
+ with(testComponent) {
+ disableFlags.disableFlags.value =
+ DisableFlagsModel(
+ StatusBarManager.DISABLE_NONE,
+ StatusBarManager.DISABLE2_NONE,
+ )
+ assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
+ }
+
+ @Test
+ fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() =
+ with(testComponent) {
+ disableFlags.disableFlags.value =
+ DisableFlagsModel(
+ StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
+ StatusBarManager.DISABLE2_NONE,
+ )
+ assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
index f28d9ab06bc7..a0faab563452 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationLaunchAnimationInteractorTest.kt
@@ -14,42 +14,44 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.data.repository
+package com.android.systemui.statusbar.notification.domain.interactor
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
@SmallTest
-class NotificationExpansionRepositoryTest : SysuiTestCase() {
- private val underTest = NotificationExpansionRepository()
+class NotificationLaunchAnimationInteractorTest : SysuiTestCase() {
+ private val repository = NotificationLaunchAnimationRepository()
+ private val underTest = NotificationLaunchAnimationInteractor(repository)
@Test
- fun setIsExpandAnimationRunning_startsAsFalse() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
+ fun setIsLaunchAnimationRunning_startsAsFalse() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
assertThat(latest).isFalse()
}
@Test
- fun setIsExpandAnimationRunning_false_emitsTrue() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
+ fun setIsLaunchAnimationRunning_false_emitsTrue() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
- underTest.setIsExpandAnimationRunning(true)
+ underTest.setIsLaunchAnimationRunning(true)
assertThat(latest).isTrue()
}
@Test
- fun setIsExpandAnimationRunning_false_emitsFalse() = runTest {
- val latest by collectLastValue(underTest.isExpandAnimationRunning)
- underTest.setIsExpandAnimationRunning(true)
+ fun setIsLaunchAnimationRunning_false_emitsFalse() = runTest {
+ val latest by collectLastValue(underTest.isLaunchAnimationRunning)
+ underTest.setIsLaunchAnimationRunning(true)
// WHEN the animation is no longer running
- underTest.setIsExpandAnimationRunning(false)
+ underTest.setIsLaunchAnimationRunning(false)
// THEN the flow emits false
assertThat(latest).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
deleted file mode 100644
index fe49016a5d45..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsInteractorTest.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.domain.interactor
-
-import android.app.StatusBarManager
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.log.LogBufferFactory
-import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.statusbar.disableflags.DisableFlagsLogger
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepositoryImpl
-import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mockito.verify
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-class NotificationsInteractorTest : SysuiTestCase() {
-
- private lateinit var underTest: NotificationsInteractor
-
- private val testScope = TestScope(UnconfinedTestDispatcher())
- private val commandQueue: CommandQueue = mock()
- private val logBuffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
- private val disableFlagsLogger = DisableFlagsLogger()
- private lateinit var disableFlagsRepository: DisableFlagsRepository
-
- @Before
- fun setUp() {
- disableFlagsRepository =
- DisableFlagsRepositoryImpl(
- commandQueue,
- DISPLAY_ID,
- testScope.backgroundScope,
- mock(),
- logBuffer,
- disableFlagsLogger,
- )
- underTest = NotificationsInteractor(disableFlagsRepository)
- }
-
- @Test
- fun disableFlags_notifAlertsNotDisabled_notifAlertsEnabledTrue() {
- val callback = getCommandQueueCallback()
-
- callback.disable(
- DISPLAY_ID,
- StatusBarManager.DISABLE_NONE,
- StatusBarManager.DISABLE2_NONE,
- /* animate= */ false
- )
-
- assertThat(underTest.areNotificationAlertsEnabled()).isTrue()
- }
-
- @Test
- fun disableFlags_notifAlertsDisabled_notifAlertsEnabledFalse() {
- val callback = getCommandQueueCallback()
-
- callback.disable(
- DISPLAY_ID,
- StatusBarManager.DISABLE_NOTIFICATION_ALERTS,
- StatusBarManager.DISABLE2_NONE,
- /* animate= */ false
- )
-
- assertThat(underTest.areNotificationAlertsEnabled()).isFalse()
- }
-
- private fun getCommandQueueCallback(): CommandQueue.Callbacks {
- val callbackCaptor = argumentCaptor<CommandQueue.Callbacks>()
- verify(commandQueue).addCallback(callbackCaptor.capture())
- return callbackCaptor.value
- }
-
- private companion object {
- const val DISPLAY_ID = 1
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
new file mode 100644
index 000000000000..ca8ea4eb9d1a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+
+@SmallTest
+class RenderNotificationsListInteractorTest : SysuiTestCase() {
+
+ private val notifsRepository = ActiveNotificationListRepository()
+ private val notifsInteractor = ActiveNotificationsInteractor(notifsRepository)
+ private val underTest =
+ RenderNotificationListInteractor(
+ notifsRepository,
+ sectionStyleProvider = mock(),
+ )
+
+ @Test
+ fun setRenderedList_preservesOrdering() = runTest {
+ val notifs by collectLastValue(notifsInteractor.notifications)
+ val keys = (1..50).shuffled().map { "$it" }
+ val entries = keys.map { mock<ListEntry> { whenever(key).thenReturn(it) } }
+ underTest.setRenderedList(entries)
+ assertThat(notifs).comparingElementsUsing(byKey).containsExactlyElementsIn(keys).inOrder()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
new file mode 100644
index 000000000000..2a3c1a53559e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SeenNotificationsInteractorTest : SysuiTestCase() {
+
+ private val repository = ActiveNotificationListRepository()
+ private val underTest = SeenNotificationsInteractor(repository)
+
+ @Test
+ fun testNoFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(false)
+
+ assertThat(hasFilteredOutSeenNotifications).isFalse()
+ }
+
+ @Test
+ fun testHasFilteredOutSeenNotifications() = runTest {
+ val hasFilteredOutSeenNotifications by
+ collectLastValue(underTest.hasFilteredOutSeenNotifications)
+
+ underTest.setHasFilteredOutSeenNotifications(true)
+
+ assertThat(hasFilteredOutSeenNotifications).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index f72142ffc6d7..cc87d7ce377b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -22,8 +22,15 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,6 +38,7 @@ import android.widget.TextView;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
@@ -44,9 +52,13 @@ public class FooterViewTest extends SysuiTestCase {
FooterView mView;
+ Context mSpyContext = spy(mContext);
+
@Before
public void setUp() {
- mView = (FooterView) LayoutInflater.from(mContext).inflate(
+ mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
+
+ mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
R.layout.status_bar_notification_footer, null, false);
mView.setDuration(0);
}
@@ -102,6 +114,37 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
+ public void testSetMessageString_resourceOnlyFetchedOnce() {
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+ verify(mSpyContext).getString(eq(R.string.unlock_to_see_notif_text));
+
+ clearInvocations(mSpyContext);
+
+ assertThat(((TextView) mView.findViewById(R.id.unlock_prompt_footer))
+ .getText().toString()).contains("Unlock");
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+ mView.setMessageString(R.string.unlock_to_see_notif_text);
+
+ verify(mSpyContext, never()).getString(anyInt());
+ }
+
+ @Test
+ public void testSetMessageIcon_resourceOnlyFetchedOnce() {
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+ verify(mSpyContext).getDrawable(eq(R.drawable.ic_friction_lock_closed));
+
+ clearInvocations(mSpyContext);
+
+ // Set it a few more times, it shouldn't lead to the resource being fetched again
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+ mView.setMessageIcon(R.drawable.ic_friction_lock_closed);
+
+ verify(mSpyContext, never()).getDrawable(anyInt());
+ }
+
+ @Test
public void testSetFooterLabelVisible() {
mView.setFooterLabelVisible(true);
assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
new file mode 100644
index 000000000000..57a7c3c7e2bf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.footer.ui.viewmodel
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FooterViewModelTest : SysuiTestCase() {
+ private val repository = ActiveNotificationListRepository()
+ private val interactor = SeenNotificationsInteractor(repository)
+ private val underTest = FooterViewModel(interactor)
+
+ @Test
+ fun testMessageVisible_whenFilteredNotifications() = runTest {
+ val message by collectLastValue(underTest.message)
+
+ repository.hasFilteredOutSeenNotifications.value = true
+
+ assertThat(message?.visible).isTrue()
+ }
+
+ @Test
+ fun testMessageVisible_whenNoFilteredNotifications() = runTest {
+ val message by collectLastValue(underTest.message)
+
+ repository.hasFilteredOutSeenNotifications.value = false
+
+ assertThat(message?.visible).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
new file mode 100644
index 000000000000..ec80e5f8821c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.icon.domain.interactor
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.SysUITestModule
+import com.android.TestMocksModule
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
+import com.android.systemui.statusbar.notification.shared.byIsAmbient
+import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
+import com.android.systemui.statusbar.notification.shared.byIsPulsing
+import com.android.systemui.statusbar.notification.shared.byIsRowDismissed
+import com.android.systemui.statusbar.notification.shared.byIsSilent
+import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
+import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import java.util.Optional
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: NotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet())
+ assertThat(filteredSet).containsExactlyElementsIn(testIcons)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.filteredNotifSet())
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showAmbient = false))
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noLowPriority() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showLowPriority = false))
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showDismissed = false))
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by
+ collectLastValue(underTest.filteredNotifSet(showRepliedMessages = false))
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsNotFullyHidden() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsFullyHidden() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.filteredNotifSet(showPulsing = false))
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: AlwaysOnDisplayNotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val deviceEntryRepository: FakeDeviceEntryRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerAlwaysOnDisplayNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsNotFullyHidden_bypassDisabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(false)
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassDisabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(false)
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noPulsing_notifsNotFullyHidden_bypassEnabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(true)
+ keyguardViewStateRepository.setNotificationsFullyHidden(false)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showPulsing_notifsFullyHidden_bypassEnabled() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.aodNotifs)
+ deviceEntryRepository.setBypassEnabled(true)
+ keyguardViewStateRepository.setNotificationsFullyHidden(true)
+ assertThat(filteredSet).comparingElementsUsing(byIsPulsing).contains(true)
+ }
+ }
+}
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class StatusBarNotificationIconsInteractorTest : SysuiTestCase() {
+
+ private val bubbles: Bubbles = mock()
+
+ @Component(modules = [SysUITestModule::class])
+ @SysUISingleton
+ interface TestComponent {
+ val underTest: StatusBarNotificationIconsInteractor
+
+ val activeNotificationListRepository: ActiveNotificationListRepository
+ val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
+ val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
+ val testScope: TestScope
+
+ @Component.Factory
+ interface Factory {
+ fun create(@BindsInstance test: SysuiTestCase, mocks: TestMocksModule): TestComponent
+ }
+ }
+
+ val testComponent: TestComponent =
+ DaggerStatusBarNotificationIconsInteractorTest_TestComponent.factory()
+ .create(test = this, mocks = TestMocksModule(bubbles = Optional.of(bubbles)))
+
+ @Before
+ fun setup() =
+ with(testComponent) {
+ activeNotificationListRepository.activeNotifications.value =
+ testIcons.associateBy { it.key }
+ }
+
+ @Test
+ fun filteredEntrySet_noExpandedBubbles() =
+ with(testComponent) {
+ testScope.runTest {
+ whenever(bubbles.isBubbleExpanded(eq("notif1"))).thenReturn(true)
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byKey).doesNotContain("notif1")
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noAmbient() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet).comparingElementsUsing(byIsAmbient).doesNotContain(true)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsSuppressedFromStatusBar)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noLowPriority_whenDontShowSilentIcons() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ notificationListenerSettingsRepository.showSilentStatusIcons.value = false
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_showLowPriority_whenShowSilentIcons() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ notificationListenerSettingsRepository.showSilentStatusIcons.value = true
+ assertThat(filteredSet).comparingElementsUsing(byIsSilent).contains(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noDismissed() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsRowDismissed)
+ .doesNotContain(true)
+ }
+ }
+
+ @Test
+ fun filteredEntrySet_noRepliedMessages() =
+ with(testComponent) {
+ testScope.runTest {
+ val filteredSet by collectLastValue(underTest.statusBarNotifs)
+ assertThat(filteredSet)
+ .comparingElementsUsing(byIsLastMessageFromReply)
+ .doesNotContain(true)
+ }
+ }
+}
+
+private val testIcons =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ isAmbient = true,
+ ),
+ activeNotificationModel(
+ key = "notif3",
+ isRowDismissed = true,
+ ),
+ activeNotificationModel(
+ key = "notif4",
+ isSilent = true,
+ ),
+ activeNotificationModel(
+ key = "notif5",
+ isLastMessageFromReply = true,
+ ),
+ activeNotificationModel(
+ key = "notif6",
+ isSuppressedFromStatusBar = true,
+ ),
+ activeNotificationModel(
+ key = "notif7",
+ isPulsing = true,
+ ),
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
deleted file mode 100644
index e57986ddfa18..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconAreaControllerViewBinderWrapperImplTest.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.statusbar.notification.icon.ui.viewbinder
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import androidx.test.filters.SmallTest
-import com.android.SysUITestModule
-import com.android.TestMocksModule
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FakeFeatureFlagsClassicModule
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.phone.DozeParameters
-import com.android.systemui.user.domain.UserDomainLayerModule
-import dagger.BindsInstance
-import dagger.Component
-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
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-class NotificationIconAreaControllerViewBinderWrapperImplTest : SysuiTestCase() {
-
- @Mock private lateinit var dozeParams: DozeParameters
-
- private lateinit var testComponent: TestComponent
- private val underTest
- get() = testComponent.underTest
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- allowTestableLooperAsMainThread()
-
- testComponent =
- DaggerNotificationIconAreaControllerViewBinderWrapperImplTest_TestComponent.factory()
- .create(
- test = this,
- featureFlags =
- FakeFeatureFlagsClassicModule {
- set(Flags.FACE_AUTH_REFACTOR, value = false)
- set(Flags.MIGRATE_KEYGUARD_STATUS_VIEW, value = false)
- },
- mocks =
- TestMocksModule(
- dozeParameters = dozeParams,
- ),
- )
- }
-
- @Test
- fun testNotificationIcons_settingHideIcons() {
- underTest.settingsListener.onStatusBarIconsBehaviorChanged(true)
- assertFalse(underTest.shouldShowLowPriorityIcons())
- }
-
- @Test
- fun testNotificationIcons_settingShowIcons() {
- underTest.settingsListener.onStatusBarIconsBehaviorChanged(false)
- assertTrue(underTest.shouldShowLowPriorityIcons())
- }
-
- @SysUISingleton
- @Component(
- modules =
- [
- SysUITestModule::class,
- BiometricsDomainLayerModule::class,
- UserDomainLayerModule::class,
- ]
- )
- interface TestComponent {
-
- val underTest: NotificationIconAreaControllerViewBinderWrapperImpl
-
- @Component.Factory
- interface Factory {
- fun create(
- @BindsInstance test: SysuiTestCase,
- mocks: TestMocksModule,
- featureFlags: FakeFeatureFlagsClassicModule,
- ): TestComponent
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 31efebbc5b60..41c7071a616d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -44,7 +44,9 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.user.domain.UserDomainLayerModule
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.ui.AnimatedValue
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -243,6 +245,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
)
)
val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
keyguardRepository.setKeyguardShowing(true)
keyguardRepository.setKeyguardOccluded(false)
@@ -266,6 +269,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
fun isDozing_startAodTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.GONE,
@@ -274,13 +278,15 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isDozing_startDozeTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.GONE,
@@ -289,13 +295,15 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isFalse()
}
@Test
fun isDozing_startDozeToAodTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.DOZING,
@@ -304,13 +312,15 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isDozing?.value).isTrue()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isNotDozing_startAodToGoneTransition() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.AOD,
@@ -319,13 +329,15 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
)
)
runCurrent()
- assertThat(isDozing).isEqualTo(AnimatedValue(false, isAnimating = true))
+ assertThat(isDozing?.value).isFalse()
+ assertThat(isDozing?.isAnimating).isTrue()
}
@Test
fun isDozing_stopAnimation() =
scope.runTest {
val isDozing by collectLastValue(underTest.isDozing)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.AOD,
@@ -335,7 +347,8 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
)
runCurrent()
- underTest.completeDozeAnimation()
+ assertThat(isDozing?.isAnimating).isEqualTo(true)
+ isDozing?.stopAnimating()
runCurrent()
assertThat(isDozing?.isAnimating).isEqualTo(false)
@@ -345,6 +358,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
fun isNotVisible_pulseExpanding() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(true)
runCurrent()
@@ -355,6 +369,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
fun isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
keyguardTransitionRepository.sendTransitionStep(
TransitionStep(
to = KeyguardState.GONE,
@@ -364,13 +379,15 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
whenever(screenOffAnimController.shouldShowAodIconsWhenShade()).thenReturn(false)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(false, isAnimating = false))
+ assertThat(isVisible?.value).isFalse()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
fun isVisible_bypassEnabled() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
deviceEntryRepository.setBypassEnabled(true)
runCurrent()
@@ -381,6 +398,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
fun isNotVisible_pulseExpanding_notBypassing() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(true)
deviceEntryRepository.setBypassEnabled(false)
runCurrent()
@@ -398,26 +416,30 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
}
@Test
fun isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(false)
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
fun isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -425,7 +447,8 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = false))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
}
@Test
@@ -440,13 +463,15 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- assertThat(isVisible).isEqualTo(AnimatedValue(true, isAnimating = true))
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
}
@Test
fun isVisible_stopAnimation() =
scope.runTest {
val isVisible by collectLastValue(underTest.isVisible)
+ runCurrent()
notifsKeyguardRepository.setPulseExpanding(false)
deviceEntryRepository.setBypassEnabled(false)
whenever(dozeParams.alwaysOn).thenReturn(true)
@@ -454,7 +479,8 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
notifsKeyguardRepository.setNotificationsFullyHidden(true)
runCurrent()
- underTest.completeVisibilityAnimation()
+ assertThat(isVisible?.isAnimating).isEqualTo(true)
+ isVisible?.stopAnimating()
runCurrent()
assertThat(isVisible?.isAnimating).isEqualTo(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index e1e7f92265f0..ba68fbb981e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.graphics.Rect
+import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.SysUITestModule
@@ -34,13 +36,23 @@ import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository
+import com.android.systemui.statusbar.notification.shared.activeNotificationModel
import com.android.systemui.statusbar.phone.DozeParameters
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.user.domain.UserDomainLayerModule
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -61,18 +73,6 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
@Mock lateinit var dozeParams: DozeParameters
private lateinit var testComponent: TestComponent
- private val underTest: NotificationIconContainerStatusBarViewModel
- get() = testComponent.underTest
- private val deviceProvisioningRepository
- get() = testComponent.deviceProvisioningRepository
- private val keyguardTransitionRepository
- get() = testComponent.keyguardTransitionRepository
- private val keyguardRepository
- get() = testComponent.keyguardRepository
- private val powerRepository
- get() = testComponent.powerRepository
- private val scope
- get() = testComponent.scope
@Before
fun setup() {
@@ -82,7 +82,6 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory()
.create(
test = this,
- // Configurable bindings
featureFlags =
FakeFeatureFlagsClassicModule {
set(Flags.FACE_AUTH_REFACTOR, value = false)
@@ -93,155 +92,299 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
dozeParameters = dozeParams,
),
)
-
- keyguardRepository.setKeyguardShowing(false)
- deviceProvisioningRepository.setFactoryResetProtectionActive(false)
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.OTHER,
- lastSleepReason = WakeSleepReason.OTHER,
- )
+ .apply {
+ keyguardRepository.setKeyguardShowing(false)
+ deviceProvisioningRepository.setFactoryResetProtectionActive(false)
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.OTHER,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ }
}
@Test
fun animationsEnabled_isFalse_whenFrpIsActive() =
- scope.runTest {
- deviceProvisioningRepository.setFactoryResetProtectionActive(true)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ deviceProvisioningRepository.setFactoryResetProtectionActive(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_AOD,
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_AOD,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- to = DozeStateModel.DOZE_PULSING,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ to = DozeStateModel.DOZE_PULSING,
+ )
+ )
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.STARTING_TO_SLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isFalse()
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(false)
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isFalse()
+ }
}
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.STARTING_TO_SLEEP,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
)
- )
- whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+ whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isTrue_whenNotAsleep() =
- scope.runTest {
- powerRepository.updateWakefulness(
- rawState = WakefulnessState.AWAKE,
- lastWakeReason = WakeSleepReason.POWER_BUTTON,
- lastSleepReason = WakeSleepReason.OTHER,
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ with(testComponent) {
+ scope.runTest {
+ powerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.OTHER,
+ )
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- runCurrent()
- assertThat(animationsEnabled).isTrue()
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ runCurrent()
+ assertThat(animationsEnabled).isTrue()
+ }
}
@Test
fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() =
- scope.runTest {
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ with(testComponent) {
+ scope.runTest {
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ )
)
- )
- keyguardRepository.setKeyguardShowing(true)
- runCurrent()
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ assertThat(animationsEnabled).isFalse()
+
+ keyguardRepository.setKeyguardShowing(false)
+ runCurrent()
+
+ assertThat(animationsEnabled).isTrue()
+ }
+ }
+
+ @Test
+ fun iconColors_testsDarkBounds() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ emptyList(),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ assertThat(iconColorsLookup).isNotNull()
+
+ val iconColors = iconColorsLookup?.iconColors(Rect())
+ assertThat(iconColors).isNotNull()
+ iconColors!!
+
+ assertThat(iconColors.tint).isEqualTo(0xAABBCC)
+
+ val staticDrawableColor = iconColors.staticDrawableColor(Rect(), isColorized = true)
+
+ assertThat(staticDrawableColor).isEqualTo(0xAABBCC)
+ }
+ }
+
+ @Test
+ fun iconColors_staticDrawableColor_nonColorized() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ emptyList(),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect())
+ val staticDrawableColor =
+ iconColors?.staticDrawableColor(Rect(), isColorized = false)
+ assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+ }
+ }
+
+ @Test
+ fun iconColors_staticDrawableColor_isColorized_notInDarkTintArea() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ listOf(Rect(0, 0, 5, 5)),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4))
+ val staticDrawableColor =
+ iconColors?.staticDrawableColor(Rect(6, 6, 7, 7), isColorized = true)
+ assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
+ }
+ }
+
+ @Test
+ fun iconColors_notInDarkTintArea() =
+ with(testComponent) {
+ scope.runTest {
+ darkIconRepository.darkState.value =
+ SysuiDarkIconDispatcher.DarkChange(
+ listOf(Rect(0, 0, 5, 5)),
+ 0f,
+ 0xAABBCC,
+ )
+ val iconColorsLookup by collectLastValue(underTest.iconColors)
+ val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7))
+ assertThat(iconColors).isNull()
+ }
+ }
+
+ @Test
+ fun isolatedIcon_animateOnAppear_shadeCollapsed() =
+ with(testComponent) {
+ scope.runTest {
+ val icon: Icon = mock()
+ shadeRepository.setLegacyShadeExpansion(0f)
+ activeNotificationsRepository.activeNotifications.value =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
+ )
+ .associateBy { it.key }
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
- assertThat(animationsEnabled).isFalse()
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ runCurrent()
+
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ assertThat(isolatedIcon?.isAnimating).isTrue()
+ }
+ }
+
+ @Test
+ fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() =
+ with(testComponent) {
+ scope.runTest {
+ val icon: Icon = mock()
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ activeNotificationsRepository.activeNotifications.value =
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ groupKey = "group",
+ statusBarIcon = icon
+ )
+ )
+ .associateBy { it.key }
+ val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+ runCurrent()
- keyguardRepository.setKeyguardShowing(false)
- runCurrent()
+ headsUpViewStateRepository.isolatedNotification.value = "notif1"
+ runCurrent()
- assertThat(animationsEnabled).isTrue()
+ assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+ assertThat(isolatedIcon?.isAnimating).isFalse()
+ }
}
@SysUISingleton
@@ -249,7 +392,6 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
modules =
[
SysUITestModule::class,
- // Real impls
BiometricsDomainLayerModule::class,
UserDomainLayerModule::class,
]
@@ -258,10 +400,14 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
val underTest: NotificationIconContainerStatusBarViewModel
+ val activeNotificationsRepository: ActiveNotificationListRepository
+ val darkIconRepository: FakeDarkIconRepository
val deviceProvisioningRepository: FakeDeviceProvisioningRepository
+ val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository
val keyguardTransitionRepository: FakeKeyguardTransitionRepository
val keyguardRepository: FakeKeyguardRepository
val powerRepository: FakePowerRepository
+ val shadeRepository: FakeShadeRepository
val scope: TestScope
@Component.Factory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
index a0f50486ffff..4bb28ae46211 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt
@@ -50,8 +50,8 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
@Test
fun testViewWalker_plainNotification_withPublicView() {
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888))
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val publicIcon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
testHelper.setDefaultInflationFlags(NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL)
val row =
testHelper.createRow(
@@ -122,9 +122,9 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
@Test
fun testViewWalker_bigPictureNotification() {
- val bigPicture = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(60, 60, Bitmap.Config.ARGB_8888))
+ val bigPicture = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val largeIcon = Icon.createWithBitmap(Bitmap.createBitmap(30, 30, Bitmap.Config.ARGB_8888))
val row =
testHelper.createRow(
Notification.Builder(mContext)
@@ -182,8 +182,8 @@ class NotificationMemoryViewWalkerTest : SysuiTestCase() {
@Test
fun testViewWalker_customView() {
- val icon = Icon.createWithBitmap(Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888))
- val bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888)
+ val icon = Icon.createWithBitmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
+ val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888)
val views = RemoteViews(mContext.packageName, R.layout.custom_view_dark)
views.setImageViewBitmap(R.id.custom_view_dark_image, bitmap)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
index 23ae26c255ce..1bb7b61668e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
@@ -24,9 +24,9 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.widget.NotificationDrawableConsumer
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -45,6 +45,7 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
private const val FREE_IMAGE_DELAY_MS = 4000L
+private const val MAX_IMAGE_SIZE = 512 // size of the test drawables in pixels
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -81,6 +82,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
@Before
fun setUp() {
allowTestableLooperAsMainThread()
+ overrideMaxImageSizes()
iconManager =
BigPictureIconManager(
context,
@@ -430,6 +432,17 @@ class BigPictureIconManagerTest : SysuiTestCase() {
verifyZeroInteractions(mockConsumer)
}
+ private fun overrideMaxImageSizes() {
+ testableResources.addOverride(
+ com.android.internal.R.dimen.notification_big_picture_max_width,
+ MAX_IMAGE_SIZE
+ )
+ testableResources.addOverride(
+ com.android.internal.R.dimen.notification_big_picture_max_height,
+ MAX_IMAGE_SIZE
+ )
+ }
+
private fun assertIsPlaceHolder(drawable: Drawable) {
assertThat(drawable).isInstanceOf(PlaceHolderDrawable::class.java)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
new file mode 100644
index 000000000000..ca105f3e52ea
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shared/TestActiveNotificationModel.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import android.graphics.drawable.Icon
+import com.google.common.truth.Correspondence
+
+val byKey: Correspondence<ActiveNotificationModel, String> =
+ Correspondence.transforming({ it?.key }, "has a key of")
+val byIsAmbient: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isAmbient }, "has an isAmbient value of")
+val byIsSuppressedFromStatusBar: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming(
+ { it?.isSuppressedFromStatusBar },
+ "has an isSuppressedFromStatusBar value of",
+ )
+val byIsSilent: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isSilent }, "has an isSilent value of")
+val byIsRowDismissed: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isRowDismissed }, "has an isRowDismissed value of")
+val byIsLastMessageFromReply: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming(
+ { it?.isLastMessageFromReply },
+ "has an isLastMessageFromReply value of"
+ )
+val byIsPulsing: Correspondence<ActiveNotificationModel, Boolean> =
+ Correspondence.transforming({ it?.isPulsing }, "has an isPulsing value of")
+
+fun activeNotificationModel(
+ key: String,
+ groupKey: String? = null,
+ isAmbient: Boolean = false,
+ isRowDismissed: Boolean = false,
+ isSilent: Boolean = false,
+ isLastMessageFromReply: Boolean = false,
+ isSuppressedFromStatusBar: Boolean = false,
+ isPulsing: Boolean = false,
+ aodIcon: Icon? = null,
+ shelfIcon: Icon? = null,
+ statusBarIcon: Icon? = null,
+) =
+ ActiveNotificationModel(
+ key = key,
+ groupKey = groupKey,
+ isAmbient = isAmbient,
+ isRowDismissed = isRowDismissed,
+ isSilent = isSilent,
+ isLastMessageFromReply = isLastMessageFromReply,
+ isSuppressedFromStatusBar = isSuppressedFromStatusBar,
+ isPulsing = isPulsing,
+ aodIcon = aodIcon,
+ shelfIcon = shelfIcon,
+ statusBarIcon = statusBarIcon,
+ )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 20197e3ed547..3dafb23c8a37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -82,13 +82,13 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
-import com.android.systemui.statusbar.notification.stack.data.repository.NotificationListRepository;
-import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationListInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
@@ -171,8 +171,8 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
- private final NotificationListInteractor mNotificationListInteractor =
- new NotificationListInteractor(new NotificationListRepository());
+ private final SeenNotificationsInteractor mSeenNotificationsInteractor =
+ new SeenNotificationsInteractor(new ActiveNotificationListRepository());
private NotificationStackScrollLayoutController mController;
@@ -504,7 +504,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Test
public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
initController(/* viewIsAttached= */ true);
- mNotificationListInteractor.setHasFilteredOutSeenNotifications(true);
+ mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
verify(mNotificationStackScrollLayout).updateFooter();
@@ -704,7 +704,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
- mNotificationListInteractor,
+ mSeenNotificationsInteractor,
mShadeController,
mJankMonitor,
mStackLogger,
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 033c96ae84b0..8f36d4f5bf6c 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
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.stack;
import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;
+import static com.android.systemui.Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR;
+import static com.android.systemui.flags.SetFlagsRuleExtensionsKt.setFlagDefault;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -165,6 +167,11 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mFeatureFlags.setDefault(Flags.NOTIFICATION_SHELF_REFACTOR);
mFeatureFlags.setDefault(Flags.NEW_AOD_TRANSITION);
mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX);
+ // Some tests in this file test the FooterView. Since we're refactoring the FooterView
+ // business logic out of the NSSL, the behavior tested in this file will eventually be
+ // tested directly in the new FooterView stack. For now, we just want to make sure that the
+ // old behavior is preserved when the flag is off.
+ setFlagDefault(mSetFlagsRule, FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR);
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 49906dca0344..08ef47765174 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -62,6 +62,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
)
private val testableResources = mContext.getOrCreateTestableResources()
+ private val maxPanelHeight =
+ mContext.resources.displayMetrics.heightPixels -
+ px(R.dimen.notification_panel_margin_top) -
+ px(R.dimen.notification_panel_margin_bottom)
private fun px(@DimenRes id: Int): Float =
testableResources.resources.getDimensionPixelSize(id).toFloat()
@@ -147,7 +151,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
stackScrollAlgorithm.initView(context)
hostView.removeAllViews()
hostView.addView(emptyShadeView)
- ambientState.layoutMaxHeight = 1280
+ ambientState.layoutMaxHeight = maxPanelHeight.toInt()
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
@@ -155,7 +159,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
context.resources.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom)
val fullHeight = ambientState.layoutMaxHeight + marginBottom - ambientState.stackY
val centeredY = ambientState.stackY + fullHeight / 2f - emptyShadeView.height / 2f
- assertThat(emptyShadeView.viewState?.yTranslation).isEqualTo(centeredY)
+ assertThat(emptyShadeView.viewState.yTranslation).isEqualTo(centeredY)
}
@Test
@@ -356,7 +360,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
whenever(notificationRow.canViewBeCleared()).thenReturn(false)
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.addView(footerView)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -370,7 +374,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
whenever(notificationRow.canViewBeCleared()).thenReturn(true)
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.addView(footerView)
stackScrollAlgorithm.resetViewStates(ambientState, 0)
@@ -382,7 +386,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() {
ambientState.isClearAllInProgress = true
ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = 1000f // plenty space for the footer in the stack
+ ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
hostView.removeAllViews() // remove all rows
hostView.addView(footerView)
@@ -1006,7 +1010,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
private fun resetViewStates_stackMargin_changesHunYTranslation() {
- val stackTopMargin = 50
+ val stackTopMargin = bigGap.toInt() // a gap smaller than the headsUpInset
val headsUpTranslationY = stackScrollAlgorithm.mHeadsUpInset - stackTopMargin
// we need the shelf to mock the real-life behaviour of StackScrollAlgorithm#updateChild
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 68f2728c9ace..7de05add2884 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -19,11 +19,14 @@ import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
import android.testing.AndroidTestingRunner
+import android.view.View
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.LaunchableView
import com.android.systemui.assist.AssistManager
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -41,6 +44,7 @@ import com.android.systemui.statusbar.window.StatusBarWindowController
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.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -49,6 +53,7 @@ import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.mock
@@ -116,16 +121,51 @@ class ActivityStarterImplTest : SysuiTestCase() {
@Test
fun startPendingIntentDismissingKeyguard_keyguardShowing_dismissWithAction() {
val pendingIntent = mock(PendingIntent::class.java)
+ whenever(pendingIntent.isActivity).thenReturn(true)
whenever(keyguardStateController.isShowing).thenReturn(true)
whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
underTest.startPendingIntentDismissingKeyguard(pendingIntent)
+ mainExecutor.runAllReady()
verify(statusBarKeyguardViewManager)
.dismissWithAction(any(OnDismissAction::class.java), eq(null), anyBoolean(), eq(null))
}
@Test
+ fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ val parent = FrameLayout(context)
+ val view =
+ object : View(context), LaunchableView {
+ override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+ }
+ parent.addView(view)
+ val controller = ActivityLaunchAnimator.Controller.fromView(view)
+ whenever(pendingIntent.isActivity).thenReturn(true)
+ whenever(keyguardStateController.isShowing).thenReturn(true)
+ whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ whenever(activityIntentHelper.wouldPendingShowOverLockscreen(eq(pendingIntent), anyInt()))
+ .thenReturn(true)
+
+ underTest.startPendingIntentMaybeDismissingKeyguard(
+ intent = pendingIntent,
+ animationController = controller,
+ intentSentUiThreadCallback = null,
+ )
+ mainExecutor.runAllReady()
+
+ verify(activityLaunchAnimator)
+ .startPendingIntentWithAnimation(
+ nullable(),
+ eq(true),
+ nullable(),
+ eq(true),
+ any(),
+ )
+ }
+
+ @Test
fun startPendingIntentDismissingKeyguard_associatedView_getAnimatorController() {
val pendingIntent = mock(PendingIntent::class.java)
val associatedView = mock(ExpandableNotificationRow::class.java)
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 c8cbe42fb0d5..41eaf858f27a 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
@@ -158,7 +158,6 @@ import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -277,8 +276,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mNotificationShadeWindowViewControllerLazy;
@Mock private NotificationShelfController mNotificationShelfController;
@Mock private DozeParameters mDozeParameters;
- @Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
- @Mock private LockscreenWallpaper mLockscreenWallpaper;
@Mock private DozeServiceHost mDozeServiceHost;
@Mock private BackActionInteractor mBackActionInteractor;
@Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
@@ -404,7 +401,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
when(mGradientColors.supportsDarkText()).thenReturn(true);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
- when(mLockscreenWallpaperLazy.get()).thenReturn(mLockscreenWallpaper);
when(mBiometricUnlockControllerLazy.get()).thenReturn(mBiometricUnlockController);
when(mCameraLauncherLazy.get()).thenReturn(mCameraLauncher);
when(mNotificationShadeWindowViewControllerLazy.get())
@@ -505,10 +501,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
(Lazy<NotificationPresenter>) () -> mNotificationPresenter,
(Lazy<NotificationActivityStarter>) () -> mNotificationActivityStarter,
mNotifLaunchAnimControllerProvider,
- new NotificationExpansionRepository(),
mDozeParameters,
mScrimController,
- mLockscreenWallpaperLazy,
mBiometricUnlockControllerLazy,
mAuthRippleController,
mDozeServiceHost,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 593c587c0f51..472709cd622e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -42,6 +42,8 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
import com.android.systemui.shade.NotificationShadeWindowViewController;
@@ -96,18 +98,21 @@ public class DozeServiceHostTest extends SysuiTestCase {
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private AuthController mAuthController;
@Mock private DozeHost.Callback mCallback;
-
@Mock private DozeInteractor mDozeInteractor;
+
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
mDozeServiceHost = new DozeServiceHost(mDozeLog, mPowerManager, mWakefullnessLifecycle,
- mStatusBarStateController, mDeviceProvisionedController, mHeadsUpManager,
- mBatteryController, mScrimController, () -> mBiometricUnlockController,
- () -> mAssistManager, mDozeScrimController,
- mKeyguardUpdateMonitor, mPulseExpansionHandler,
- mNotificationShadeWindowController, mNotificationWakeUpCoordinator,
- mAuthController, mNotificationIconAreaController, mDozeInteractor);
+ mStatusBarStateController, mDeviceProvisionedController, mFeatureFlags,
+ mHeadsUpManager, mBatteryController, mScrimController,
+ () -> mBiometricUnlockController, () -> mAssistManager, mDozeScrimController,
+ mKeyguardUpdateMonitor, mPulseExpansionHandler, mNotificationShadeWindowController,
+ mNotificationWakeUpCoordinator, mAuthController, mNotificationIconAreaController,
+ mDozeInteractor);
mDozeServiceHost.initialize(
mCentralSurfaces,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index d84bb728a856..529e2c9a8e7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -34,6 +34,8 @@ import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeHeadsUpTracker;
@@ -42,6 +44,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -82,10 +85,12 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
private KeyguardStateController mKeyguardStateController;
private CommandQueue mCommandQueue;
private NotificationRoundnessManager mNotificationRoundnessManager;
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
+ mFeatureFlags.setDefault(Flags.NOTIFICATION_ICON_CONTAINER_REFACTOR);
mTestHelper = new NotificationTestHelper(
mContext,
mDependency,
@@ -119,6 +124,8 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mNotificationRoundnessManager,
mHeadsUpStatusBarView,
new Clock(mContext, null),
+ mFeatureFlags,
+ mock(HeadsUpNotificationIconInteractor.class),
Optional.of(mOperatorNameView));
mHeadsUpAppearanceController.setAppearFraction(0.0f, 0.0f);
}
@@ -203,6 +210,7 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
mNotificationRoundnessManager,
mHeadsUpStatusBarView,
new Clock(mContext, null),
+ mFeatureFlags, mock(HeadsUpNotificationIconInteractor.class),
Optional.empty());
assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
deleted file mode 100644
index 47671fbadd0a..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.app.WallpaperManager
-import android.content.pm.UserInfo
-import android.os.Looper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.user.data.model.SelectionStatus
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.kotlin.JavaAdapter
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.utils.os.FakeHandler
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito.verify
-
-@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
-class LockscreenWallpaperTest : SysuiTestCase() {
-
- private lateinit var underTest: LockscreenWallpaper
-
- private val testScope = TestScope(StandardTestDispatcher())
- private val userRepository = FakeUserRepository()
-
- private val wallpaperManager: WallpaperManager = mock()
-
- @Before
- fun setUp() {
- whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
- whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
- underTest =
- LockscreenWallpaper(
- /* wallpaperManager= */ wallpaperManager,
- /* iWallpaperManager= */ mock(),
- /* keyguardUpdateMonitor= */ mock(),
- /* dumpManager= */ mock(),
- /* mediaManager= */ mock(),
- /* mainHandler= */ FakeHandler(Looper.getMainLooper()),
- /* javaAdapter= */ JavaAdapter(testScope.backgroundScope),
- /* userRepository= */ userRepository,
- /* userTracker= */ mock(),
- )
- underTest.start()
- }
-
- @Test
- fun getBitmap_matchesUserIdFromUserRepo() =
- testScope.runTest {
- val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
- userRepository.setUserInfos(listOf(info))
- userRepository.setSelectedUserInfo(info)
-
- underTest.bitmap
-
- verify(wallpaperManager).getWallpaperFile(any(), eq(5))
- }
-
- @Test
- fun getBitmap_usesOldUserIfNewUserInProgress() =
- testScope.runTest {
- val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0)
- val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0)
- userRepository.setUserInfos(listOf(info5, info6))
- userRepository.setSelectedUserInfo(info5)
-
- // WHEN the selection of user 6 is only in progress
- userRepository.setSelectedUserInfo(
- info6,
- selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
- )
-
- underTest.bitmap
-
- // THEN we still use user 5 for wallpaper selection
- verify(wallpaperManager).getWallpaperFile(any(), eq(5))
- }
-}
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 6b3bd22d5e62..15c09b53938f 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
@@ -70,6 +70,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac
import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
@@ -141,6 +142,8 @@ public class ScrimControllerTest extends SysuiTestCase {
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
+ @Mock private AlternateBouncerToGoneTransitionViewModel
+ mAlternateBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
@Mock private CoroutineDispatcher mMainDispatcher;
@@ -264,10 +267,12 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock);
when(mDockManager.isDocked()).thenReturn(false);
- when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
+ when(mKeyguardTransitionInteractor.transition(any(), any()))
.thenReturn(emptyFlow());
when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
.thenReturn(emptyFlow());
+ when(mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha())
+ .thenReturn(emptyFlow());
mScrimController = new ScrimController(
mLightBarController,
@@ -285,6 +290,7 @@ public class ScrimControllerTest extends SysuiTestCase {
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
+ mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
mWallpaperRepository,
mMainDispatcher,
@@ -992,6 +998,7 @@ public class ScrimControllerTest extends SysuiTestCase {
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
+ mAlternateBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
mWallpaperRepository,
mMainDispatcher,
@@ -1775,7 +1782,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
- mScrimController.mPrimaryBouncerToGoneTransition.accept(
+ mScrimController.mBouncerToGoneTransition.accept(
new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
TransitionState.RUNNING, "ScrimControllerTest"));
@@ -1787,7 +1794,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
- mScrimController.mPrimaryBouncerToGoneTransition.accept(
+ mScrimController.mBouncerToGoneTransition.accept(
new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
TransitionState.FINISHED, "ScrimControllerTest"));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index beac995c893b..1e3197730626 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -86,7 +86,8 @@ import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorCon
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.NotificationExpansionRepository;
+import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -222,7 +223,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
HeadsUpManager headsUpManager = mock(HeadsUpManager.class);
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider =
new NotificationLaunchAnimatorControllerProvider(
- new NotificationExpansionRepository(),
+ new NotificationLaunchAnimationInteractor(
+ new NotificationLaunchAnimationRepository()),
mock(NotificationListContainer.class),
headsUpManager,
mJankMonitor);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index ee4f2089c05c..53c621d24601 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -53,7 +53,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
+import com.android.systemui.statusbar.notification.domain.interactor.NotificationAlertsInteractor;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -79,8 +79,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
private CommandQueue mCommandQueue;
private FakeMetricsLogger mMetricsLogger;
private final ShadeController mShadeController = mock(ShadeController.class);
- private final NotificationsInteractor mNotificationsInteractor =
- mock(NotificationsInteractor.class);
+ private final NotificationAlertsInteractor mNotificationAlertsInteractor =
+ mock(NotificationAlertsInteractor.class);
private final KeyguardStateController mKeyguardStateController =
mock(KeyguardStateController.class);
private final InitController mInitController = new InitController();
@@ -116,7 +116,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
mKeyguardStateController,
- mNotificationsInteractor,
+ mNotificationAlertsInteractor,
mock(LockscreenShadeTransitionController.class),
mock(PowerInteractor.class),
mCommandQueue,
@@ -226,7 +226,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
.setTag("a")
.setNotification(n)
.build();
- when(mNotificationsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
+ when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false);
assertTrue("When alerts aren't enabled, interruptions are suppressed",
mInterruptSuppressor.suppressInterruptions(entry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
new file mode 100644
index 000000000000..1e628bd35053
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.statusbar.phone
+
+import android.content.res.Configuration
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class SystemUIBottomSheetDialogTest : SysuiTestCase() {
+
+ private val configurationController = mock<ConfigurationController>()
+ private val config = mock<Configuration>()
+
+ private lateinit var dialog: SystemUIBottomSheetDialog
+
+ @Before
+ fun setup() {
+ dialog = SystemUIBottomSheetDialog(mContext, configurationController)
+ }
+
+ @Test
+ fun onStart_registersConfigCallback() {
+ dialog.show()
+
+ verify(configurationController).addCallback(any())
+ }
+
+ @Test
+ fun onStop_unregisterConfigCallback() {
+ dialog.show()
+ dialog.dismiss()
+
+ verify(configurationController).removeCallback(any())
+ }
+
+ @Test
+ fun onConfigurationChanged_calledInSubclass() {
+ var onConfigChangedCalled = false
+ val subclass =
+ object : SystemUIBottomSheetDialog(mContext, configurationController) {
+ override fun onConfigurationChanged() {
+ onConfigChangedCalled = true
+ }
+ }
+
+ subclass.show()
+
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
+ captor.value.onConfigChanged(config)
+
+ assertThat(onConfigChangedCalled).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index bcb34d6d9342..9a77f0c0300d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -48,23 +48,29 @@ import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.AnimatorTestRule;
+import com.android.systemui.common.ui.ConfigurationState;
+import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarNotificationIconViewStore;
+import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerStatusBarViewModel;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
@@ -72,6 +78,7 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder;
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
@@ -682,7 +689,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mLocationPublisher,
mMockNotificationAreaController,
mShadeExpansionStateManager,
- mock(FeatureFlags.class),
+ mock(FeatureFlagsClassic.class),
mStatusBarIconController,
mIconManagerFactory,
mCollapsedStatusBarViewModel,
@@ -702,7 +709,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mExecutor,
mDumpManager,
mStatusBarWindowStateController,
- mKeyguardUpdateMonitor);
+ mKeyguardUpdateMonitor,
+ mock(NotificationIconContainerStatusBarViewModel.class),
+ mock(ConfigurationState.class),
+ mock(ConfigurationController.class),
+ mock(DozeParameters.class),
+ mock(ScreenOffAnimationController.class),
+ mock(StatusBarNotificationIconViewStore.class),
+ mock(DemoModeController.class));
}
private void setUpDaggerComponent() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
new file mode 100644
index 000000000000..4eb159103b49
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceStateRepositoryTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.unfold.updates
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.system.DeviceStateRepositoryImpl
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DeviceStateRepositoryTest : SysuiTestCase() {
+
+ private val foldProvider = mock<FoldProvider>()
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
+ private val foldStateRepository = DeviceStateRepositoryImpl(foldProvider) { r -> r.run() }
+
+ @Test
+ fun onHingeAngleUpdate_received() =
+ testScope.runTest {
+ val flowValue = collectLastValue(foldStateRepository.isFolded)
+ val foldCallback = argumentCaptor<FoldProvider.FoldCallback>()
+
+ verify(foldProvider).registerCallback(capture(foldCallback), any())
+
+ foldCallback.value.onFoldUpdated(true)
+ assertThat(flowValue()).isEqualTo(true)
+
+ foldCallback.value.onFoldUpdated(false)
+ assertThat(flowValue()).isEqualTo(false)
+ }
+
+ @Test
+ fun onHingeAngleUpdate_unregisters() {
+ testScope.runTest {
+ val flowValue = collectLastValue(foldStateRepository.isFolded)
+
+ verify(foldProvider).registerCallback(any(), any())
+ }
+ verify(foldProvider).unregisterCallback(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
new file mode 100644
index 000000000000..065132300564
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.unfold.updates
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FoldStateRepositoryTest : SysuiTestCase() {
+
+ private val foldStateProvider = mock<FoldStateProvider>()
+ private val foldUpdatesListener = argumentCaptor<FoldStateProvider.FoldUpdatesListener>()
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
+ private val foldStateRepository = FoldStateRepositoryImpl(foldStateProvider)
+ @Test
+ fun onHingeAngleUpdate_received() =
+ testScope.runTest {
+ val flowValue = collectLastValue(foldStateRepository.hingeAngle)
+
+ verify(foldStateProvider).addCallback(capture(foldUpdatesListener))
+ foldUpdatesListener.value.onHingeAngleUpdate(42f)
+
+ assertThat(flowValue()).isEqualTo(42f)
+ }
+
+ @Test
+ fun onFoldUpdate_received() =
+ testScope.runTest {
+ val flowValue = collectLastValue(foldStateRepository.foldUpdate)
+
+ verify(foldStateProvider).addCallback(capture(foldUpdatesListener))
+ foldUpdatesListener.value.onFoldUpdate(FOLD_UPDATE_START_OPENING)
+
+ assertThat(flowValue()).isEqualTo(FoldUpdate.START_OPENING)
+ }
+
+ @Test
+ fun foldUpdates_mappedCorrectly() {
+ mapOf(
+ FOLD_UPDATE_START_OPENING to FoldUpdate.START_OPENING,
+ FOLD_UPDATE_START_CLOSING to FoldUpdate.START_CLOSING,
+ FOLD_UPDATE_FINISH_HALF_OPEN to FoldUpdate.FINISH_HALF_OPEN,
+ FOLD_UPDATE_FINISH_FULL_OPEN to FoldUpdate.FINISH_FULL_OPEN,
+ FOLD_UPDATE_FINISH_CLOSED to FoldUpdate.FINISH_CLOSED
+ )
+ .forEach { (id, expected) ->
+ assertThat(FoldUpdate.fromFoldUpdateId(id)).isEqualTo(expected)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt
new file mode 100644
index 000000000000..1c8465a482de
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/LayoutInflaterUtilTest.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.view.reinflateAndBindLatest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LayoutInflaterUtilTest : SysuiTestCase() {
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+ private var inflationCount = 0
+ private var callbackCount = 0
+ @Mock private lateinit var disposableHandle: DisposableHandle
+
+ inner class TestLayoutInflater : LayoutInflater(context) {
+ override fun inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View {
+ inflationCount++
+ return View(context)
+ }
+
+ override fun cloneInContext(p0: Context?): LayoutInflater {
+ // not needed for this test
+ return this
+ }
+ }
+
+ val underTest = TestLayoutInflater()
+
+ @After
+ fun cleanUp() {
+ inflationCount = 0
+ callbackCount = 0
+ }
+
+ @Test
+ fun testReinflateAndBindLatest_inflatesWithoutEmission() = runTest {
+ backgroundScope.launch {
+ underTest.reinflateAndBindLatest(
+ resource = 0,
+ root = null,
+ attachToRoot = false,
+ emptyFlow<Unit>()
+ ) {
+ callbackCount++
+ null
+ }
+ }
+
+ // Inflates without an emission
+ runCurrent()
+ assertThat(inflationCount).isEqualTo(1)
+ assertThat(callbackCount).isEqualTo(1)
+ }
+
+ @Test
+ fun testReinflateAndBindLatest_reinflatesOnEmission() = runTest {
+ val observable = MutableSharedFlow<Unit>()
+ val flow = observable.asSharedFlow()
+ backgroundScope.launch {
+ underTest.reinflateAndBindLatest(
+ resource = 0,
+ root = null,
+ attachToRoot = false,
+ flow
+ ) {
+ callbackCount++
+ null
+ }
+ }
+
+ listOf(1, 2, 3).forEach { count ->
+ runCurrent()
+ assertThat(inflationCount).isEqualTo(count)
+ assertThat(callbackCount).isEqualTo(count)
+ observable.emit(Unit)
+ }
+ }
+
+ @Test
+ fun testReinflateAndBindLatest_disposesOnCancel() = runTest {
+ val job = launch {
+ underTest.reinflateAndBindLatest(
+ resource = 0,
+ root = null,
+ attachToRoot = false,
+ emptyFlow()
+ ) {
+ callbackCount++
+ disposableHandle
+ }
+ }
+
+ runCurrent()
+ job.cancelAndJoin()
+ verify(disposableHandle).dispose()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
index aaf8d0761dce..94100fe7f4c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.util.ui
import android.testing.AndroidTestingRunner
@@ -20,9 +22,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -35,64 +36,193 @@ class AnimatedValueTest : SysuiTestCase() {
@Test
fun animatableEvent_updatesValue() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = false))
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isFalse()
}
@Test
fun animatableEvent_startAnimation() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = true))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isTrue()
}
@Test
fun animatableEvent_startAnimation_alreadyAnimating() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val values = events.toAnimatedValueFlow(completionEvents = emptyFlow())
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
events.emit(AnimatableEvent(value = 2, startAnimating = true))
- assertThat(value).isEqualTo(AnimatedValue(value = 2, isAnimating = true))
+ assertThat(value?.value).isEqualTo(2)
+ assertThat(value?.isAnimating).isTrue()
}
@Test
fun animatedValue_stopAnimating() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val stopEvent = MutableSharedFlow<Unit>()
- val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
+ val values = events.toAnimatedValueFlow()
val value by collectLastValue(values)
runCurrent()
events.emit(AnimatableEvent(value = 1, startAnimating = true))
- stopEvent.emit(Unit)
+ assertThat(value?.isAnimating).isTrue()
+ value?.stopAnimating()
- assertThat(value).isEqualTo(AnimatedValue(value = 1, isAnimating = false))
+ assertThat(value?.value).isEqualTo(1)
+ assertThat(value?.isAnimating).isFalse()
}
@Test
- fun animatedValue_stopAnimating_notAnimating() = runTest {
+ fun animatedValue_stopAnimatingPrevValue_doesNothing() = runTest {
val events = MutableSharedFlow<AnimatableEvent<Int>>()
- val stopEvent = MutableSharedFlow<Unit>()
- val values = events.toAnimatedValueFlow(completionEvents = stopEvent)
- values.launchIn(backgroundScope)
+ val values = events.toAnimatedValueFlow()
+ val value by collectLastValue(values)
runCurrent()
- events.emit(AnimatableEvent(value = 1, startAnimating = false))
+ events.emit(AnimatableEvent(value = 1, startAnimating = true))
+ val prevValue = value
+ assertThat(prevValue?.isAnimating).isTrue()
+
+ events.emit(AnimatableEvent(value = 2, startAnimating = true))
+ assertThat(value?.isAnimating).isTrue()
+ prevValue?.stopAnimating()
+
+ assertThat(value?.value).isEqualTo(2)
+ assertThat(value?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun zipValues_applyTransform() {
+ val animating = AnimatedValue.Animating(1) {}
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(animating, notAnimating) { a, b -> a + b }
+ assertThat(sum.value).isEqualTo(3)
+ }
+
+ @Test
+ fun zipValues_firstIsAnimating_resultIsAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(1) { stopped = true }
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(animating, notAnimating) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_secondIsAnimating_resultIsAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(1) { stopped = true }
+ val notAnimating = AnimatedValue.NotAnimating(2)
+ val sum = zip(notAnimating, animating) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun zipValues_bothAnimating_resultIsAnimating() {
+ var firstStopped = false
+ var secondStopped = false
+ val first = AnimatedValue.Animating(1) { firstStopped = true }
+ val second = AnimatedValue.Animating(2) { secondStopped = true }
+ val sum = zip(first, second) { a, b -> a + b }
+ assertThat(sum.isAnimating).isTrue()
+
+ sum.stopAnimating()
+ assertThat(firstStopped).isTrue()
+ assertThat(secondStopped).isTrue()
+ }
- assertThat(stopEvent.subscriptionCount.value).isEqualTo(0)
+ @Test
+ fun zipValues_neitherAnimating_resultIsNotAnimating() {
+ val first = AnimatedValue.NotAnimating(1)
+ val second = AnimatedValue.NotAnimating(2)
+ val sum = zip(first, second) { a, b -> a + b }
+ assertThat(sum.isAnimating).isFalse()
+ }
+
+ @Test
+ fun mapAnimatedValue_isAnimating() {
+ var stopped = false
+ val animating = AnimatedValue.Animating(3) { stopped = true }
+ val squared = animating.map { it * it }
+ assertThat(squared.value).isEqualTo(9)
+ assertThat(squared.isAnimating).isTrue()
+ squared.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun mapAnimatedValue_notAnimating() {
+ val notAnimating = AnimatedValue.NotAnimating(3)
+ val squared = notAnimating.map { it * it }
+ assertThat(squared.value).isEqualTo(9)
+ assertThat(squared.isAnimating).isFalse()
+ }
+
+ @Test
+ fun flattenAnimatingValue_neitherAnimating() {
+ val nested = AnimatedValue.NotAnimating(AnimatedValue.NotAnimating(10))
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isFalse()
+ }
+
+ @Test
+ fun flattenAnimatingValue_outerAnimating() {
+ var stopped = false
+ val inner = AnimatedValue.NotAnimating(10)
+ val nested = AnimatedValue.Animating(inner) { stopped = true }
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun flattenAnimatingValue_innerAnimating() {
+ var stopped = false
+ val inner = AnimatedValue.Animating(10) { stopped = true }
+ val nested = AnimatedValue.NotAnimating(inner)
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(stopped).isTrue()
+ }
+
+ @Test
+ fun flattenAnimatingValue_bothAnimating() {
+ var innerStopped = false
+ var outerStopped = false
+ val inner = AnimatedValue.Animating(10) { innerStopped = true }
+ val nested = AnimatedValue.Animating(inner) { outerStopped = true }
+ val flattened = nested.flatten()
+ assertThat(flattened.value).isEqualTo(10)
+ assertThat(flattened.isAnimating).isTrue()
+ flattened.stopAnimating()
+ assertThat(innerStopped).isTrue()
+ assertThat(outerStopped).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 468c5a73645b..fc2030f694ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -16,15 +16,20 @@
package com.android.systemui.wallpapers;
+import static android.app.WallpaperManager.FLAG_LOCK;
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -111,7 +116,8 @@ public class ImageWallpaperTest extends SysuiTestCase {
when(mWallpaperBitmap.getConfig()).thenReturn(Bitmap.Config.ARGB_8888);
// set up wallpaper manager
- when(mWallpaperManager.getBitmapAsUser(eq(ActivityManager.getCurrentUser()), anyBoolean()))
+ when(mWallpaperManager.getBitmapAsUser(
+ eq(ActivityManager.getCurrentUser()), anyBoolean(), anyInt(), anyBoolean()))
.thenReturn(mWallpaperBitmap);
when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
@@ -208,6 +214,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
ImageWallpaper.CanvasEngine spyEngine = spy(engine);
doNothing().when(spyEngine).drawFrameOnCanvas(any(Bitmap.class));
doNothing().when(spyEngine).reportEngineShown(anyBoolean());
+ doReturn(FLAG_SYSTEM | FLAG_LOCK).when(spyEngine).getWallpaperFlags();
doAnswer(invocation -> {
((ImageWallpaper.CanvasEngine) invocation.getMock()).onMiniBitmapUpdated();
return null;
@@ -216,7 +223,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
- when(mWallpaperManager.peekBitmapDimensions())
+ when(mWallpaperManager.peekBitmapDimensions(anyInt(), anyBoolean()))
.thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth);
when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight);
@@ -234,9 +241,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
clearInvocations(mSurfaceHolder);
setBitmapDimensions(bitmapWidth, bitmapHeight);
- ImageWallpaper imageWallpaper = createImageWallpaper();
- ImageWallpaper.CanvasEngine engine =
- (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
+ ImageWallpaper.CanvasEngine engine = getSpyEngine();
engine.onCreate(mSurfaceHolder);
verify(mSurfaceHolder, times(1)).setFixedSize(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 5dcc7423ecc6..8353cf78d983 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -34,7 +34,10 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor
private val _scaleForResolution = MutableStateFlow(1f)
override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
- suspend fun onAnyConfigurationChange() {
+ private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
+ private val colors = mutableMapOf<Int, MutableStateFlow<Int>>()
+
+ fun onAnyConfigurationChange() {
_onAnyConfigurationChange.tryEmit(Unit)
}
@@ -42,12 +45,12 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor
_scaleForResolution.value = scale
}
- override fun getResolutionScale(): Float {
- return _scaleForResolution.value
- }
+ override fun getResolutionScale(): Float = _scaleForResolution.value
+
+ override fun getDimensionPixelSize(id: Int): Int = pixelSizes[id]?.value ?: 0
- override fun getDimensionPixelSize(id: Int): Int {
- return 0
+ fun setDimensionPixelSize(id: Int, pixelSize: Int) {
+ pixelSizes.getOrPut(id) { MutableStateFlow(pixelSize) }.value = pixelSize
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 30132f7747b7..08adda32eb6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -1,7 +1,8 @@
package com.android.systemui.communal.data.repository
import com.android.systemui.communal.data.model.CommunalWidgetMetadata
-import com.android.systemui.communal.shared.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalAppWidgetInfo
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -11,7 +12,14 @@ class FakeCommunalWidgetRepository : CommunalWidgetRepository {
override val stopwatchAppWidgetInfo: Flow<CommunalAppWidgetInfo?> = _stopwatchAppWidgetInfo
override var communalWidgetAllowlist: List<CommunalWidgetMetadata> = emptyList()
+ private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
+ override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
+
fun setStopwatchAppWidgetInfo(appWidgetInfo: CommunalAppWidgetInfo) {
_stopwatchAppWidgetInfo.value = appWidgetInfo
}
+
+ fun setCommunalWidgets(inventory: List<CommunalWidgetContentModel>) {
+ _communalWidgets.value = inventory
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 5cd09d8df429..5fd0b4f61b43 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -43,9 +43,10 @@ fun createPendingDisplay(id: Int = 0): DisplayRepository.PendingDisplay =
mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
/** Fake [DisplayRepository] implementation for testing. */
-class FakeDisplayRepository() : DisplayRepository {
- private val flow = MutableSharedFlow<Set<Display>>()
- private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
+class FakeDisplayRepository : DisplayRepository {
+ private val flow = MutableSharedFlow<Set<Display>>(replay = 1)
+ private val pendingDisplayFlow =
+ MutableSharedFlow<DisplayRepository.PendingDisplay?>(replay = 1)
/** Emits [value] as [displays] flow value. */
suspend fun emit(value: Set<Display>) = flow.emit(value)
@@ -59,7 +60,7 @@ class FakeDisplayRepository() : DisplayRepository {
override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
get() = pendingDisplayFlow
- private val _displayChangeEvent = MutableSharedFlow<Int>()
+ private val _displayChangeEvent = MutableSharedFlow<Int>(replay = 1)
override val displayChangeEvent: Flow<Int> = _displayChangeEvent
suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index e160548fed78..71e2bc1339a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -42,7 +42,7 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio
_transitions.emit(step)
}
- override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
+ override fun startTransition(info: TransitionInfo): UUID? {
return if (info.animator == null) UUID.randomUUID() else null
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
index 1cb4ab76c9d5..55935961466d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -19,62 +19,35 @@ package com.android.systemui.qs.tiles.base.interactor
import javax.annotation.CheckReturnValue
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
class FakeQSTileDataInteractor<T>(
- private val dataFlow: MutableSharedFlow<FakeData<T>> =
- MutableSharedFlow(replay = Int.MAX_VALUE),
+ private val dataFlow: MutableSharedFlow<T> = MutableSharedFlow(replay = Int.MAX_VALUE),
private val availabilityFlow: MutableSharedFlow<Boolean> =
MutableSharedFlow(replay = Int.MAX_VALUE),
) : QSTileDataInteractor<T> {
- private val mutableDataRequests = mutableListOf<QSTileDataRequest>()
- val dataRequests: List<QSTileDataRequest> = mutableDataRequests
+ private val mutableDataRequests = mutableListOf<DataRequest>()
+ val dataRequests: List<DataRequest> = mutableDataRequests
- private val mutableAvailabilityRequests = mutableListOf<Unit>()
- val availabilityRequests: List<Unit> = mutableAvailabilityRequests
+ private val mutableAvailabilityRequests = mutableListOf<AvailabilityRequest>()
+ val availabilityRequests: List<AvailabilityRequest> = mutableAvailabilityRequests
- @CheckReturnValue
- fun emitData(data: T): FilterEmit =
- object : FilterEmit {
- override fun forRequest(request: QSTileDataRequest): Boolean =
- dataFlow.tryEmit(FakeData(data, DataFilter.ForRequest(request)))
- override fun forAnyRequest(): Boolean = dataFlow.tryEmit(FakeData(data, DataFilter.Any))
- }
+ @CheckReturnValue fun emitData(data: T): Boolean = dataFlow.tryEmit(data)
fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
- override fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<T> {
- mutableDataRequests.add(qsTileDataRequest)
- return dataFlow
- .filter {
- when (it.filter) {
- is DataFilter.Any -> true
- is DataFilter.ForRequest -> it.filter.request == qsTileDataRequest
- }
- }
- .map { it.data }
+ override fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<T> {
+ mutableDataRequests.add(DataRequest(userId))
+ return triggers.flatMapLatest { dataFlow }
}
- override fun availability(): Flow<Boolean> {
- mutableAvailabilityRequests.add(Unit)
+ override fun availability(userId: Int): Flow<Boolean> {
+ mutableAvailabilityRequests.add(AvailabilityRequest(userId))
return availabilityFlow
}
- interface FilterEmit {
- fun forRequest(request: QSTileDataRequest): Boolean
- fun forAnyRequest(): Boolean
- }
-
- class FakeData<T>(
- val data: T,
- val filter: DataFilter,
- )
-
- sealed class DataFilter {
- object Any : DataFilter()
- class ForRequest(val request: QSTileDataRequest) : DataFilter()
- }
+ data class DataRequest(val userId: Int)
+ data class AvailabilityRequest(val userId: Int)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
index 9c99cb52d5ee..597d52dcb299 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -16,22 +16,19 @@
package com.android.systemui.qs.tiles.base.interactor
-import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> {
private val mutex: Mutex = Mutex()
- private val mutableInputs: MutableList<FakeInput<T>> = mutableListOf()
+ private val mutableInputs: MutableList<QSTileInput<T>> = mutableListOf()
- val inputs: List<FakeInput<T>> = mutableInputs
+ val inputs: List<QSTileInput<T>> = mutableInputs
- fun lastInput(): FakeInput<T>? = inputs.lastOrNull()
+ fun lastInput(): QSTileInput<T>? = inputs.lastOrNull()
- override suspend fun handleInput(userAction: QSTileUserAction, currentData: T) {
- mutex.withLock { mutableInputs.add(FakeInput(userAction, currentData)) }
+ override suspend fun handleInput(input: QSTileInput<T>) {
+ mutex.withLock { mutableInputs.add(input) }
}
-
- data class FakeInput<T>(val userAction: QSTileUserAction, val data: T)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 4307ff980be4..7494ccf32a2c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -69,10 +69,16 @@ class FakeUserTracker(
_userId = _userInfo.id
_userHandle = UserHandle.of(_userId)
+ onBeforeUserSwitching()
onUserChanging()
onUserChanged()
}
+ fun onBeforeUserSwitching(userId: Int = _userId) {
+ val copy = callbacks.toList()
+ copy.forEach { it.onBeforeUserSwitching(userId) }
+ }
+
fun onUserChanging(userId: Int = _userId) {
val copy = callbacks.toList()
copy.forEach { it.onUserChanging(userId, userContext) {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
index e59f642071fb..8f18e1331427 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/FakeStatusBarDataLayerModule.kt
@@ -15,8 +15,10 @@
*/
package com.android.systemui.statusbar.data
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepositoryModule
import com.android.systemui.statusbar.disableflags.data.FakeStatusBarDisableFlagsDataLayerModule
import com.android.systemui.statusbar.notification.data.FakeStatusBarNotificationsDataLayerModule
+import com.android.systemui.statusbar.phone.data.FakeStatusBarPhoneDataLayerModule
import com.android.systemui.statusbar.pipeline.data.FakeStatusBarPipelineDataLayerModule
import com.android.systemui.statusbar.policy.data.FakeStatusBarPolicyDataLayerModule
import dagger.Module
@@ -25,7 +27,9 @@ import dagger.Module
includes =
[
FakeStatusBarDisableFlagsDataLayerModule::class,
+ FakeStatusBarModeRepositoryModule::class,
FakeStatusBarNotificationsDataLayerModule::class,
+ FakeStatusBarPhoneDataLayerModule::class,
FakeStatusBarPipelineDataLayerModule::class,
FakeStatusBarPolicyDataLayerModule::class,
]
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 61ba4649f559..f25d282208f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -18,17 +18,17 @@ package com.android.systemui.statusbar.data.repository
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
-import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
-class FakeStatusBarModeRepository : StatusBarModeRepository {
+class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepository {
override val isTransientShown = MutableStateFlow(false)
override val isInFullscreenMode = MutableStateFlow(false)
override val statusBarAppearance = MutableStateFlow<StatusBarAppearance?>(null)
override val statusBarMode = MutableStateFlow(StatusBarMode.TRANSPARENT)
- override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {}
-
override fun showTransient() {
isTransientShown.value = true
}
@@ -36,3 +36,8 @@ class FakeStatusBarModeRepository : StatusBarModeRepository {
isTransientShown.value = false
}
}
+
+@Module
+interface FakeStatusBarModeRepositoryModule {
+ @Binds fun bindFake(fake: FakeStatusBarModeRepository): StatusBarModeRepository
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt
new file mode 100644
index 000000000000..d2c3b7a090bd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/FakeStatusBarPhoneDataLayerModule.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.phone.data
+
+import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepositoryModule
+import dagger.Module
+
+@Module(includes = [FakeDarkIconRepositoryModule::class]) object FakeStatusBarPhoneDataLayerModule
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
new file mode 100644
index 000000000000..50d3f0a106f2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/data/repository/FakeDarkIconRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar.phone.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher.DarkChange
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+@SysUISingleton
+class FakeDarkIconRepository @Inject constructor() : DarkIconRepository {
+ override val darkState = MutableStateFlow(DarkChange(emptyList(), 0f, 0))
+}
+
+@Module
+interface FakeDarkIconRepositoryModule {
+ @Binds fun bindFake(fake: FakeDarkIconRepository): DarkIconRepository
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index 5ffc094b88b3..7473ca6a6486 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -22,6 +22,8 @@ import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgress
import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider
+import com.android.systemui.unfold.updates.FoldStateRepository
+import com.android.systemui.unfold.updates.FoldStateRepositoryImpl
import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.hinge.HingeSensorAngleProvider
@@ -55,6 +57,12 @@ class UnfoldSharedModule {
fun unfoldKeyguardVisibilityManager(
impl: UnfoldKeyguardVisibilityManagerImpl
): UnfoldKeyguardVisibilityManager = impl
+
+ @Provides
+ @Singleton
+ fun foldStateRepository(
+ impl: FoldStateRepositoryImpl
+ ): FoldStateRepository = impl
}
/**
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 6743515c2ec7..003013e18583 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
@@ -17,7 +17,6 @@ package com.android.systemui.unfold.updates
import android.content.Context
import android.os.Handler
-import android.os.Trace
import android.util.Log
import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
@@ -130,7 +129,6 @@ constructor(
"lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
)
}
- Trace.setCounter("DeviceFoldStateProvider#onHingeAngle", angle.toLong())
val currentDirection =
if (angle < lastHingeAngle) FOLD_UPDATE_START_CLOSING else FOLD_UPDATE_START_OPENING
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
index 6e87beeb295f..ea6786e6c4bc 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
@@ -20,7 +20,7 @@ interface FoldProvider {
fun registerCallback(callback: FoldCallback, executor: Executor)
fun unregisterCallback(callback: FoldCallback)
- interface FoldCallback {
+ fun interface FoldCallback {
fun onFoldUpdated(isFolded: Boolean)
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
new file mode 100644
index 000000000000..61b0b40a55bf
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.unfold.updates
+
+import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import javax.inject.Inject
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+
+/**
+ * Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
+ * start folding/unfolding, screen availability
+ */
+interface FoldStateRepository {
+ /** Latest fold update, as described by [FoldStateProvider.FoldUpdate]. */
+ val foldUpdate: Flow<FoldUpdate>
+
+ /** Provides the hinge angle while the fold/unfold is in progress. */
+ val hingeAngle: Flow<Float>
+
+ enum class FoldUpdate {
+ START_OPENING,
+ START_CLOSING,
+ FINISH_HALF_OPEN,
+ FINISH_FULL_OPEN,
+ FINISH_CLOSED;
+
+ companion object {
+ /** Maps the old [FoldStateProvider.FoldUpdate] to [FoldStateRepository.FoldUpdate]. */
+ fun fromFoldUpdateId(@FoldStateProvider.FoldUpdate oldId: Int): FoldUpdate {
+ return when (oldId) {
+ FOLD_UPDATE_START_OPENING -> START_OPENING
+ FOLD_UPDATE_START_CLOSING -> START_CLOSING
+ FOLD_UPDATE_FINISH_HALF_OPEN -> FINISH_HALF_OPEN
+ FOLD_UPDATE_FINISH_FULL_OPEN -> FINISH_FULL_OPEN
+ FOLD_UPDATE_FINISH_CLOSED -> FINISH_CLOSED
+ else -> error("FoldUpdateNotFound")
+ }
+ }
+ }
+ }
+}
+
+class FoldStateRepositoryImpl
+@Inject
+constructor(
+ private val foldStateProvider: FoldStateProvider,
+) : FoldStateRepository {
+
+ override val hingeAngle: Flow<Float>
+ get() =
+ callbackFlow {
+ val callback =
+ object : FoldStateProvider.FoldUpdatesListener {
+ override fun onHingeAngleUpdate(angle: Float) {
+ trySend(angle)
+ }
+ }
+ foldStateProvider.addCallback(callback)
+ awaitClose { foldStateProvider.removeCallback(callback) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
+
+ override val foldUpdate: Flow<FoldUpdate>
+ get() =
+ callbackFlow {
+ val callback =
+ object : FoldStateProvider.FoldUpdatesListener {
+ override fun onFoldUpdate(update: Int) {
+ trySend(FoldUpdate.fromFoldUpdateId(update))
+ }
+ }
+ foldStateProvider.addCallback(callback)
+ awaitClose { foldStateProvider.removeCallback(callback) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
+}
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 3406102b28ac..98421a9e1d3e 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -367,11 +367,7 @@ public class WallpaperBackupAgent extends BackupAgent {
ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
mSystemHasLiveComponent = wpService != null;
- ComponentName kwpService = null;
- boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
- if (lockscreenLiveWallpaper) {
- kwpService = parseWallpaperComponent(infoStage, "kwp");
- }
+ ComponentName kwpService = parseWallpaperComponent(infoStage, "kwp");
mLockHasLiveComponent = kwpService != null;
boolean separateLockWallpaper = mLockHasLiveComponent || lockImageStage.exists();
@@ -381,17 +377,16 @@ public class WallpaperBackupAgent extends BackupAgent {
// It is valid for the imagery to be absent; it means that we were not permitted
// to back up the original image on the source device, or there was no user-supplied
// wallpaper image present.
- if (!lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
if (lockImageStageExists) {
restoreFromStage(lockImageStage, infoStage, "kwp", FLAG_LOCK);
}
- if (lockscreenLiveWallpaper) restoreFromStage(imageStage, infoStage, "wp", sysWhich);
+ restoreFromStage(imageStage, infoStage, "wp", sysWhich);
// And reset to the wallpaper service we should be using
- if (lockscreenLiveWallpaper && mLockHasLiveComponent) {
- updateWallpaperComponent(kwpService, false, FLAG_LOCK);
+ if (mLockHasLiveComponent) {
+ updateWallpaperComponent(kwpService, FLAG_LOCK);
}
- updateWallpaperComponent(wpService, !lockImageStageExists, sysWhich);
+ updateWallpaperComponent(wpService, sysWhich);
} catch (Exception e) {
Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
mEventLogger.onRestoreException(e);
@@ -410,36 +405,24 @@ public class WallpaperBackupAgent extends BackupAgent {
}
@VisibleForTesting
- void updateWallpaperComponent(ComponentName wpService, boolean applyToLock, int which)
+ void updateWallpaperComponent(ComponentName wpService, int which)
throws IOException {
- boolean lockscreenLiveWallpaper = mWallpaperManager.isLockscreenLiveWallpaperEnabled();
if (servicePackageExists(wpService)) {
Slog.i(TAG, "Using wallpaper service " + wpService);
- if (lockscreenLiveWallpaper) {
- mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
- if ((which & FLAG_LOCK) != 0) {
- mEventLogger.onLockLiveWallpaperRestored(wpService);
- }
- if ((which & FLAG_SYSTEM) != 0) {
- mEventLogger.onSystemLiveWallpaperRestored(wpService);
- }
- return;
- }
- mWallpaperManager.setWallpaperComponent(wpService);
- if (applyToLock) {
- // We have a live wallpaper and no static lock image,
- // allow live wallpaper to show "through" on lock screen.
- mWallpaperManager.clear(FLAG_LOCK);
+ mWallpaperManager.setWallpaperComponentWithFlags(wpService, which);
+ if ((which & FLAG_LOCK) != 0) {
mEventLogger.onLockLiveWallpaperRestored(wpService);
}
- mEventLogger.onSystemLiveWallpaperRestored(wpService);
+ if ((which & FLAG_SYSTEM) != 0) {
+ mEventLogger.onSystemLiveWallpaperRestored(wpService);
+ }
} else {
// If we've restored a live wallpaper, but the component doesn't exist,
// we should log it as an error so we can easily identify the problem
// in reports from users
if (wpService != null) {
// TODO(b/268471749): Handle delayed case
- applyComponentAtInstall(wpService, applyToLock, which);
+ applyComponentAtInstall(wpService, which);
Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
+ " Will try to apply later");
}
@@ -579,21 +562,17 @@ public class WallpaperBackupAgent extends BackupAgent {
// Intentionally blank
}
- private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock,
- int which) {
+ private void applyComponentAtInstall(ComponentName componentName, int which) {
PackageMonitor packageMonitor = getWallpaperPackageMonitor(
- componentName, applyToLock, which);
+ componentName, which);
packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
}
@VisibleForTesting
- PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock,
- int which) {
+ PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
return new PackageMonitor() {
@Override
public void onPackageAdded(String packageName, int uid) {
- boolean lockscreenLiveWallpaper =
- mWallpaperManager.isLockscreenLiveWallpaperEnabled();
if (!isDeviceInRestore()) {
// We don't want to reapply the wallpaper outside a restore.
unregister();
@@ -601,9 +580,11 @@ public class WallpaperBackupAgent extends BackupAgent {
// We have finished restore and not succeeded, so let's log that as an error.
WallpaperEventLogger logger = new WallpaperEventLogger(
mBackupManager.getDelayedRestoreLogger());
- logger.onSystemLiveWallpaperRestoreFailed(
- WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
- if (applyToLock) {
+ if ((which & FLAG_SYSTEM) != 0) {
+ logger.onSystemLiveWallpaperRestoreFailed(
+ WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
+ }
+ if ((which & FLAG_LOCK) != 0) {
logger.onLockLiveWallpaperRestoreFailed(
WallpaperEventLogger.ERROR_LIVE_PACKAGE_NOT_INSTALLED);
}
@@ -614,37 +595,27 @@ public class WallpaperBackupAgent extends BackupAgent {
if (componentName.getPackageName().equals(packageName)) {
Slog.d(TAG, "Applying component " + componentName);
- boolean success = lockscreenLiveWallpaper
- ? mWallpaperManager.setWallpaperComponentWithFlags(componentName, which)
- : mWallpaperManager.setWallpaperComponent(componentName);
+ boolean success = mWallpaperManager.setWallpaperComponentWithFlags(
+ componentName, which);
WallpaperEventLogger logger = new WallpaperEventLogger(
mBackupManager.getDelayedRestoreLogger());
if (success) {
- if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+ if ((which & FLAG_SYSTEM) != 0) {
logger.onSystemLiveWallpaperRestored(componentName);
}
- if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+ if ((which & FLAG_LOCK) != 0) {
logger.onLockLiveWallpaperRestored(componentName);
}
} else {
- if (!lockscreenLiveWallpaper || (which & FLAG_SYSTEM) != 0) {
+ if ((which & FLAG_SYSTEM) != 0) {
logger.onSystemLiveWallpaperRestoreFailed(
WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
}
- if (lockscreenLiveWallpaper && (which & FLAG_LOCK) != 0) {
+ if ((which & FLAG_LOCK) != 0) {
logger.onLockLiveWallpaperRestoreFailed(
WallpaperEventLogger.ERROR_SET_COMPONENT_EXCEPTION);
}
}
- if (applyToLock && !lockscreenLiveWallpaper) {
- try {
- mWallpaperManager.clear(FLAG_LOCK);
- logger.onLockLiveWallpaperRestored(componentName);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
- logger.onLockLiveWallpaperRestoreFailed(e.getClass().getName());
- }
- }
// We're only expecting to restore the wallpaper component once.
unregister();
mBackupManager.reportDelayedRestoreResult(logger.getBackupRestoreLogger());
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index dc1126edde41..4c224fb33b26 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -116,8 +116,6 @@ public class WallpaperBackupAgentTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
-
- when(mWallpaperManager.isLockscreenLiveWallpaperEnabled()).thenReturn(true);
when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_SYSTEM))).thenReturn(true);
when(mWallpaperManager.isWallpaperBackupEligible(eq(FLAG_LOCK))).thenReturn(true);
@@ -363,25 +361,19 @@ public class WallpaperBackupAgentTest {
@Test
public void testUpdateWallpaperComponent_doesApplyLater() throws IOException {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
-
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
- /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
/* uid */0);
- if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
- verify(mWallpaperManager, times(1))
- .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
- verify(mWallpaperManager, never())
- .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
- verify(mWallpaperManager, never())
- .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
- verify(mWallpaperManager, never()).clear(anyInt());
- } else {
- verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
- verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
- }
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+ verify(mWallpaperManager, never()).clear(anyInt());
}
@Test
@@ -390,24 +382,19 @@ public class WallpaperBackupAgentTest {
mWallpaperBackupAgent.mIsDeviceInRestore = true;
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
- /* applyToLock */ false, FLAG_SYSTEM);
+ /* which */ FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
/* uid */0);
- if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
- verify(mWallpaperManager, times(1))
- .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
- verify(mWallpaperManager, never())
- .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
- verify(mWallpaperManager, never())
- .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
- verify(mWallpaperManager, never()).clear(anyInt());
- } else {
- verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
- verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
- }
+ verify(mWallpaperManager, times(1))
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_SYSTEM);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK);
+ verify(mWallpaperManager, never())
+ .setWallpaperComponentWithFlags(mWallpaperComponent, FLAG_LOCK | FLAG_SYSTEM);
+ verify(mWallpaperManager, never()).clear(anyInt());
}
@Test
@@ -416,7 +403,7 @@ public class WallpaperBackupAgentTest {
mWallpaperBackupAgent.mIsDeviceInRestore = false;
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
- /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -432,7 +419,7 @@ public class WallpaperBackupAgentTest {
mWallpaperBackupAgent.mIsDeviceInRestore = false;
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
- /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate "wrong" wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
@@ -622,7 +609,7 @@ public class WallpaperBackupAgentTest {
}
@Test
- public void testOnRestore_systemWallpaperImgSuccess_logsSuccess() throws Exception {
+ public void testOnRestore_wallpaperImgSuccess_logsSuccess() throws Exception {
mockStagedWallpaperFile(WALLPAPER_INFO_STAGE);
mockStagedWallpaperFile(SYSTEM_WALLPAPER_STAGE);
mWallpaperBackupAgent.onCreate(USER_HANDLE, BackupAnnotations.BackupDestination.CLOUD,
@@ -630,17 +617,16 @@ public class WallpaperBackupAgentTest {
mWallpaperBackupAgent.onRestoreFinished();
+ // wallpaper will be applied to home & lock screen, a success for both screens in expected
DataTypeResult result = getLoggingResult(WALLPAPER_IMG_SYSTEM,
mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
assertThat(result).isNotNull();
assertThat(result.getSuccessCount()).isEqualTo(1);
- if (mWallpaperManager.isLockscreenLiveWallpaperEnabled()) {
- result = getLoggingResult(WALLPAPER_IMG_LOCK,
- mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
- assertThat(result).isNotNull();
- assertThat(result.getSuccessCount()).isEqualTo(1);
- }
+ result = getLoggingResult(WALLPAPER_IMG_LOCK,
+ mWallpaperBackupAgent.getBackupRestoreEventLogger().getLoggingResults());
+ assertThat(result).isNotNull();
+ assertThat(result.getSuccessCount()).isEqualTo(1);
}
@Test
@@ -758,7 +744,7 @@ public class WallpaperBackupAgentTest {
mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
- /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
/* uid */0);
@@ -782,7 +768,7 @@ public class WallpaperBackupAgentTest {
mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
- /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
/* uid */0);
@@ -804,7 +790,7 @@ public class WallpaperBackupAgentTest {
mWallpaperBackupAgent.setBackupManagerForTesting(mBackupManager);
mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
- /* applyToLock */ true, FLAG_LOCK | FLAG_SYSTEM);
+ /* which */ FLAG_LOCK | FLAG_SYSTEM);
// Imitate wallpaper component installation.
mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
@@ -938,10 +924,8 @@ public class WallpaperBackupAgentTest {
}
@Override
- PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
- boolean applyToLock, int which) {
- mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(
- componentName, applyToLock, which);
+ PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, int which) {
+ mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, which);
return mWallpaperPackageMonitor;
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 1a735f89a2bc..75ecdb78fe00 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -1,38 +1,40 @@
package: "com.android.server.accessibility"
+# NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors.
+
flag {
- name: "proxy_use_apps_on_virtual_device_listener"
+ name: "add_window_token_without_lock"
namespace: "accessibility"
- description: "Fixes race condition described in b/286587811"
- bug: "286587811"
+ description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock"
+ bug: "297972548"
}
flag {
- name: "enable_magnification_multiple_finger_multiple_tap_gesture"
+ name: "deprecate_package_list_observer"
namespace: "accessibility"
- description: "Whether to enable multi-finger-multi-tap gesture for magnification"
- bug: "257274411"
+ description: "Stops using the deprecated PackageListObserver."
+ bug: "304561459"
}
flag {
- name: "enable_magnification_joystick"
+ name: "disable_continuous_shortcut_on_force_stop"
namespace: "accessibility"
- description: "Whether to enable joystick controls for magnification"
- bug: "297211257"
+ description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
+ bug: "198018180"
}
flag {
- name: "send_a11y_events_based_on_state"
+ name: "enable_magnification_joystick"
namespace: "accessibility"
- description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
- bug: "295575684"
+ description: "Whether to enable joystick controls for magnification"
+ bug: "297211257"
}
flag {
- name: "add_window_token_without_lock"
+ name: "enable_magnification_multiple_finger_multiple_tap_gesture"
namespace: "accessibility"
- description: "Calls WMS.addWindowToken without holding A11yManagerService#mLock"
- bug: "297972548"
+ description: "Whether to enable multi-finger-multi-tap gesture for magnification"
+ bug: "257274411"
}
flag {
@@ -43,17 +45,17 @@ flag {
}
flag {
- name: "disable_continuous_shortcut_on_force_stop"
+ name: "proxy_use_apps_on_virtual_device_listener"
namespace: "accessibility"
- description: "When a package is force stopped, remove the button shortcuts of any continuously-running shortcuts."
- bug: "198018180"
+ description: "Fixes race condition described in b/286587811"
+ bug: "286587811"
}
flag {
- name: "deprecate_package_list_observer"
+ name: "reduce_touch_exploration_sensitivity"
namespace: "accessibility"
- description: "Stops using the deprecated PackageListObserver."
- bug: "304561459"
+ description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
+ bug: "303677860"
}
flag {
@@ -64,8 +66,8 @@ flag {
}
flag {
- name: "reduce_touch_exploration_sensitivity"
+ name: "send_a11y_events_based_on_state"
namespace: "accessibility"
- description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
- bug: "303677860"
+ description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
+ bug: "295575684"
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index e65a185adfd2..87f9cf10f824 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4929,8 +4929,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.TOUCH_EXPLORATION_ENABLED);
- private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
+ private final Uri mMagnificationmSingleFingerTripleTapEnabledUri = Settings.Secure
+ .getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -4987,7 +4987,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void register(ContentResolver contentResolver) {
contentResolver.registerContentObserver(mTouchExplorationEnabledUri,
false, this, UserHandle.USER_ALL);
- contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
+ contentResolver.registerContentObserver(mMagnificationmSingleFingerTripleTapEnabledUri,
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mAutoclickEnabledUri,
false, this, UserHandle.USER_ALL);
@@ -5035,7 +5035,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (readTouchExplorationEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
- } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
+ } else if (mMagnificationmSingleFingerTripleTapEnabledUri.equals(uri)) {
if (readMagnificationEnabledSettingsLocked(userState)) {
onUserStateChangedLocked(userState);
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 65975e44ee2a..76ebdf4d4c1c 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -103,9 +103,8 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
-import com.android.server.contentprotection.ContentProtectionPackageManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -207,9 +206,6 @@ public class ContentCaptureManagerService extends
boolean mDevCfgEnableContentProtectionReceiver;
@GuardedBy("mLock")
- int mDevCfgContentProtectionAppsBlocklistSize;
-
- @GuardedBy("mLock")
int mDevCfgContentProtectionBufferSize;
@GuardedBy("mLock")
@@ -237,7 +233,7 @@ public class ContentCaptureManagerService extends
@Nullable private final ComponentName mContentProtectionServiceComponentName;
- @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+ @Nullable private final ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
@Nullable private final ContentProtectionConsentManager mContentProtectionConsentManager;
@@ -287,17 +283,15 @@ public class ContentCaptureManagerService extends
if (getEnableContentProtectionReceiverLocked()) {
mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
if (mContentProtectionServiceComponentName != null) {
- mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
- mContentProtectionBlocklistManager.updateBlocklist(
- mDevCfgContentProtectionAppsBlocklistSize);
+ mContentProtectionAllowlistManager = createContentProtectionAllowlistManager();
mContentProtectionConsentManager = createContentProtectionConsentManager();
} else {
- mContentProtectionBlocklistManager = null;
+ mContentProtectionAllowlistManager = null;
mContentProtectionConsentManager = null;
}
} else {
mContentProtectionServiceComponentName = null;
- mContentProtectionBlocklistManager = null;
+ mContentProtectionAllowlistManager = null;
mContentProtectionConsentManager = null;
}
}
@@ -445,8 +439,6 @@ public class ContentCaptureManagerService extends
case ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER:
case ContentCaptureManager.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_BUFFER_SIZE:
- case ContentCaptureManager
- .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_REQUIRED_GROUPS_CONFIG:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_CONFIG:
case DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_OPTIONAL_GROUPS_THRESHOLD:
@@ -502,14 +494,6 @@ public class ContentCaptureManagerService extends
ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_ENABLE_CONTENT_PROTECTION_RECEIVER,
ContentCaptureManager.DEFAULT_ENABLE_CONTENT_PROTECTION_RECEIVER);
- mDevCfgContentProtectionAppsBlocklistSize =
- DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
- ContentCaptureManager
- .DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
- ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
- // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
- // it immutable at this point
mDevCfgContentProtectionBufferSize =
DeviceConfig.getInt(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -542,7 +526,7 @@ public class ContentCaptureManagerService extends
+ mDevCfgMaxBufferSize
+ ", idleFlush="
+ mDevCfgIdleFlushingFrequencyMs
- + ", textFluxh="
+ + ", textFlush="
+ mDevCfgTextChangeFlushingFrequencyMs
+ ", logHistory="
+ mDevCfgLogHistorySize
@@ -552,8 +536,6 @@ public class ContentCaptureManagerService extends
+ mDevCfgDisableFlushForViewTreeAppearing
+ ", enableContentProtectionReceiver="
+ mDevCfgEnableContentProtectionReceiver
- + ", contentProtectionAppsBlocklistSize="
- + mDevCfgContentProtectionAppsBlocklistSize
+ ", contentProtectionBufferSize="
+ mDevCfgContentProtectionBufferSize
+ ", contentProtectionRequiredGroupsConfig="
@@ -844,9 +826,6 @@ public class ContentCaptureManagerService extends
pw.print("enableContentProtectionReceiver: ");
pw.println(mDevCfgEnableContentProtectionReceiver);
pw.print(prefix2);
- pw.print("contentProtectionAppsBlocklistSize: ");
- pw.println(mDevCfgContentProtectionAppsBlocklistSize);
- pw.print(prefix2);
pw.print("contentProtectionBufferSize: ");
pw.println(mDevCfgContentProtectionBufferSize);
pw.print(prefix2);
@@ -877,9 +856,8 @@ public class ContentCaptureManagerService extends
/** @hide */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
- protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
- return new ContentProtectionBlocklistManager(
- new ContentProtectionPackageManager(getContext()));
+ protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+ return new ContentProtectionAllowlistManager();
}
/** @hide */
@@ -1429,7 +1407,7 @@ public class ContentCaptureManagerService extends
private boolean isContentProtectionReceiverEnabled(
@UserIdInt int userId, @NonNull String packageName) {
if (mContentProtectionServiceComponentName == null
- || mContentProtectionBlocklistManager == null
+ || mContentProtectionAllowlistManager == null
|| mContentProtectionConsentManager == null) {
return false;
}
@@ -1443,7 +1421,7 @@ public class ContentCaptureManagerService extends
}
}
return mContentProtectionConsentManager.isConsentGranted(userId)
- && mContentProtectionBlocklistManager.isAllowed(packageName);
+ && mContentProtectionAllowlistManager.isAllowed(packageName);
}
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
new file mode 100644
index 000000000000..59af5263fa9e
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionAllowlistManager.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+
+/**
+ * Manages whether the content protection is enabled for an app using a allowlist.
+ *
+ * @hide
+ */
+public class ContentProtectionAllowlistManager {
+
+ private static final String TAG = "ContentProtectionAllowlistManager";
+
+ public ContentProtectionAllowlistManager() {}
+
+ /** Returns true if the package is allowed. */
+ public boolean isAllowed(@NonNull String packageName) {
+ Slog.v(TAG, packageName);
+ return false;
+ }
+}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
deleted file mode 100644
index a0fd28b3f279..000000000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.PackageInfo;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Manages whether the content protection is enabled for an app using a blocklist.
- *
- * @hide
- */
-public class ContentProtectionBlocklistManager {
-
- private static final String TAG = "ContentProtectionBlocklistManager";
-
- private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
- "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
- @NonNull private final ContentProtectionPackageManager mContentProtectionPackageManager;
-
- @Nullable private Set<String> mPackageNameBlocklist;
-
- public ContentProtectionBlocklistManager(
- @NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
- mContentProtectionPackageManager = contentProtectionPackageManager;
- }
-
- public boolean isAllowed(@NonNull String packageName) {
- if (mPackageNameBlocklist == null) {
- // List not loaded or failed to load, don't run on anything
- return false;
- }
- if (mPackageNameBlocklist.contains(packageName)) {
- return false;
- }
- PackageInfo packageInfo = mContentProtectionPackageManager.getPackageInfo(packageName);
- if (packageInfo == null) {
- return false;
- }
- if (!mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo)) {
- return false;
- }
- if (mContentProtectionPackageManager.isSystemApp(packageInfo)) {
- return false;
- }
- if (mContentProtectionPackageManager.isUpdatedSystemApp(packageInfo)) {
- return false;
- }
- return true;
- }
-
- public void updateBlocklist(int blocklistSize) {
- Slog.i(TAG, "Blocklist size updating to: " + blocklistSize);
- mPackageNameBlocklist = readPackageNameBlocklist(blocklistSize);
- }
-
- @Nullable
- private Set<String> readPackageNameBlocklist(int blocklistSize) {
- if (blocklistSize <= 0) {
- // Explicitly requested an empty blocklist
- return Collections.emptySet();
- }
- List<String> lines = readLinesFromRawFile(PACKAGE_NAME_BLOCKLIST_FILENAME);
- if (lines == null) {
- return null;
- }
- return lines.stream().limit(blocklistSize).collect(Collectors.toSet());
- }
-
- @VisibleForTesting
- @Nullable
- protected List<String> readLinesFromRawFile(@NonNull String filename) {
- try (FileReader fileReader = new FileReader(filename);
- BufferedReader bufferedReader = new BufferedReader(fileReader)) {
- return bufferedReader
- .lines()
- .map(line -> line.trim())
- .filter(line -> !line.isBlank())
- .collect(Collectors.toList());
- } catch (Exception ex) {
- Slog.e(TAG, "Failed to read: " + filename, ex);
- return null;
- }
- }
-}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
deleted file mode 100644
index 4ebac07ec3ea..000000000000
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.util.Slog;
-
-import java.util.Arrays;
-
-/**
- * Basic package manager for content protection using content capture.
- *
- * @hide
- */
-public class ContentProtectionPackageManager {
-
- private static final String TAG = "ContentProtectionPackageManager";
-
- private static final PackageInfoFlags PACKAGE_INFO_FLAGS =
- PackageInfoFlags.of(PackageManager.GET_PERMISSIONS);
-
- @NonNull private final PackageManager mPackageManager;
-
- public ContentProtectionPackageManager(@NonNull Context context) {
- mPackageManager = context.getPackageManager();
- }
-
- @Nullable
- public PackageInfo getPackageInfo(@NonNull String packageName) {
- try {
- return mPackageManager.getPackageInfo(packageName, PACKAGE_INFO_FLAGS);
- } catch (NameNotFoundException ex) {
- Slog.w(TAG, "Package info not found for: " + packageName, ex);
- return null;
- }
- }
-
- public boolean isSystemApp(@NonNull PackageInfo packageInfo) {
- return packageInfo.applicationInfo != null && isSystemApp(packageInfo.applicationInfo);
- }
-
- private boolean isSystemApp(@NonNull ApplicationInfo applicationInfo) {
- return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
- }
-
- public boolean isUpdatedSystemApp(@NonNull PackageInfo packageInfo) {
- return packageInfo.applicationInfo != null
- && isUpdatedSystemApp(packageInfo.applicationInfo);
- }
-
- private boolean isUpdatedSystemApp(@NonNull ApplicationInfo applicationInfo) {
- return (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
- }
-
- public boolean hasRequestedInternetPermissions(@NonNull PackageInfo packageInfo) {
- return packageInfo.requestedPermissions != null
- && Arrays.asList(packageInfo.requestedPermissions)
- .contains(Manifest.permission.INTERNET);
- }
-}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 898cdcc30e0f..4dca5a5c807b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -148,6 +148,7 @@ java_library_static {
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
"android.hardware.vibrator-V2-java",
+ "android.nfc.flags-aconfig-java",
"app-compat-annotations",
"framework-tethering.stubs.module_lib",
"service-art.stubs.system_server",
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index beea063221fb..3af0e8c54cd2 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -386,15 +386,14 @@ class StorageManagerService extends IStorageManager.Stub
private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_STORAGE);
/**
- * mLocalUnlockedUsers affects the return value of isUserUnlocked. If
- * any value in the array changes, then the binder cache for
- * isUserUnlocked must be invalidated. When adding mutating methods to
- * WatchedLockedUsers, be sure to invalidate the cache in the new
- * methods.
+ * mCeUnlockedUsers affects the return value of {@link UserManager#isUserUnlocked}. If any
+ * value in the array changes, then the binder cache for {@link UserManager#isUserUnlocked} must
+ * be invalidated. When adding mutating methods to this class, be sure to invalidate the cache
+ * in the new methods.
*/
- private static class WatchedLockedUsers {
+ private static class WatchedUnlockedUsers {
private int[] users = EmptyArray.INT;
- public WatchedLockedUsers() {
+ public WatchedUnlockedUsers() {
invalidateIsUserUnlockedCache();
}
public void append(int userId) {
@@ -426,10 +425,14 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- /** Set of users that we know are unlocked. */
+ /** Set of users whose CE storage is unlocked. */
@GuardedBy("mLock")
- private WatchedLockedUsers mLocalUnlockedUsers = new WatchedLockedUsers();
- /** Set of users that system knows are unlocked. */
+ private WatchedUnlockedUsers mCeUnlockedUsers = new WatchedUnlockedUsers();
+
+ /**
+ * Set of users that are in the RUNNING_UNLOCKED state. This differs from {@link
+ * mCeUnlockedUsers} in that a user can be stopped but still have its CE storage unlocked.
+ */
@GuardedBy("mLock")
private int[] mSystemUnlockedUsers = EmptyArray.INT;
@@ -1144,11 +1147,10 @@ class StorageManagerService extends IStorageManager.Stub
}
}
- // If vold knows that some users have their storage unlocked already (which
- // can happen after a "userspace reboot"), then add those users to
- // mLocalUnlockedUsers. Do this right away and don't wait until
- // PHASE_BOOT_COMPLETED, since the system may unlock users before then.
- private void restoreLocalUnlockedUsers() {
+ // If vold knows that some users have their CE storage unlocked already (which can happen after
+ // a "userspace reboot"), then add those users to mCeUnlockedUsers. Do this right away and
+ // don't wait until PHASE_BOOT_COMPLETED, since the system may unlock users before then.
+ private void restoreCeUnlockedUsers() {
final int[] userIds;
try {
userIds = mVold.getUnlockedUsers();
@@ -1164,7 +1166,7 @@ class StorageManagerService extends IStorageManager.Stub
// reconnecting to vold after it crashed and was restarted, in
// which case things will be the other way around --- we'll know
// about the unlocked users but vold won't.
- mLocalUnlockedUsers.appendAll(userIds);
+ mCeUnlockedUsers.appendAll(userIds);
}
}
}
@@ -1236,7 +1238,7 @@ class StorageManagerService extends IStorageManager.Stub
private void onUserStopped(int userId) {
Slog.d(TAG, "onUserStopped " + userId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
try {
mVold.onUserStopped(userId);
@@ -1320,7 +1322,7 @@ class StorageManagerService extends IStorageManager.Stub
unlockedUsers.add(userId);
}
}
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#onUserStopped might be slow");
for (Integer userId : unlockedUsers) {
try {
@@ -2075,7 +2077,7 @@ class StorageManagerService extends IStorageManager.Stub
connectVold();
}, DateUtils.SECOND_IN_MILLIS);
} else {
- restoreLocalUnlockedUsers();
+ restoreCeUnlockedUsers();
onDaemonConnected();
}
}
@@ -2341,7 +2343,7 @@ class StorageManagerService extends IStorageManager.Stub
try {
// TODO(b/135341433): Remove cautious logging when FUSE is stable
Slog.i(TAG, "Mounting volume " + vol);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#mount might be slow");
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
@Override
@@ -2472,7 +2474,7 @@ class StorageManagerService extends IStorageManager.Stub
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
@@ -2491,7 +2493,7 @@ class StorageManagerService extends IStorageManager.Stub
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
@@ -2510,7 +2512,7 @@ class StorageManagerService extends IStorageManager.Stub
final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS, "#partition might be very slow");
try {
mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
@@ -3235,9 +3237,9 @@ class StorageManagerService extends IStorageManager.Stub
try {
mVold.createUserKey(userId, serialNumber, ephemeral);
- // New keys are always unlocked.
+ // Since the user's CE key was just created, the user's CE storage is now unlocked.
synchronized (mLock) {
- mLocalUnlockedUsers.append(userId);
+ mCeUnlockedUsers.append(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -3252,9 +3254,9 @@ class StorageManagerService extends IStorageManager.Stub
try {
mVold.destroyUserKey(userId);
- // Destroying a key also locks it.
+ // Since the user's CE key was just destroyed, the user's CE storage is now locked.
synchronized (mLock) {
- mLocalUnlockedUsers.remove(userId);
+ mCeUnlockedUsers.remove(userId);
}
} catch (Exception e) {
Slog.wtf(TAG, e);
@@ -3281,7 +3283,7 @@ class StorageManagerService extends IStorageManager.Stub
mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret));
}
synchronized (mLock) {
- mLocalUnlockedUsers.append(userId);
+ mCeUnlockedUsers.append(userId);
}
}
@@ -3310,14 +3312,14 @@ class StorageManagerService extends IStorageManager.Stub
}
synchronized (mLock) {
- mLocalUnlockedUsers.remove(userId);
+ mCeUnlockedUsers.remove(userId);
}
}
@Override
public boolean isUserKeyUnlocked(int userId) {
synchronized (mLock) {
- return mLocalUnlockedUsers.contains(userId);
+ return mCeUnlockedUsers.contains(userId);
}
}
@@ -3620,7 +3622,7 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public ParcelFileDescriptor open() throws AppFuseMountException {
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#open might be slow");
try {
final FileDescriptor fd = mVold.mountAppFuse(uid, mountId);
@@ -3634,7 +3636,7 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public ParcelFileDescriptor openFile(int mountId, int fileId, int flags)
throws AppFuseMountException {
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#openFile might be slow");
try {
return new ParcelFileDescriptor(
@@ -3646,7 +3648,7 @@ class StorageManagerService extends IStorageManager.Stub
@Override
public void close() throws Exception {
- Watchdog.getInstance().setOneOffTimeoutForMonitors(
+ Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow");
if (mMounted) {
mVold.unmountAppFuse(uid, mountId);
@@ -4716,7 +4718,7 @@ class StorageManagerService extends IStorageManager.Stub
}
pw.println();
- pw.println("Local unlocked users: " + mLocalUnlockedUsers);
+ pw.println("CE unlocked users: " + mCeUnlockedUsers);
pw.println("System unlocked users: " + Arrays.toString(mSystemUnlockedUsers));
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 55aa7164a67b..003046ab884f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -250,7 +250,7 @@ public class Watchdog implements Dumpable {
private Monitor mCurrentMonitor;
private long mStartTimeMillis;
private int mPauseCount;
- private long mOneOffTimeoutMillis;
+ private long mPauseEndTimeMillis;
HandlerChecker(Handler handler, String name) {
mHandler = handler;
@@ -270,20 +270,19 @@ public class Watchdog implements Dumpable {
* @param handlerCheckerTimeoutMillis the timeout to use for this run
*/
public void scheduleCheckLocked(long handlerCheckerTimeoutMillis) {
- if (mOneOffTimeoutMillis > 0) {
- mWaitMaxMillis = mOneOffTimeoutMillis;
- mOneOffTimeoutMillis = 0;
- } else {
- mWaitMaxMillis = handlerCheckerTimeoutMillis;
- }
+ mWaitMaxMillis = handlerCheckerTimeoutMillis;
if (mCompleted) {
// Safe to update monitors in queue, Handler is not in the middle of work
mMonitors.addAll(mMonitorQueue);
mMonitorQueue.clear();
}
+
+ long nowMillis = SystemClock.uptimeMillis();
+ boolean isPaused = mPauseCount > 0
+ || (mPauseEndTimeMillis > 0 && mPauseEndTimeMillis < nowMillis);
if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
- || (mPauseCount > 0)) {
+ || isPaused) {
// Don't schedule until after resume OR
// If the target looper has recently been polling, then
// there is no reason to enqueue our checker on it since that
@@ -301,7 +300,8 @@ public class Watchdog implements Dumpable {
mCompleted = false;
mCurrentMonitor = null;
- mStartTimeMillis = SystemClock.uptimeMillis();
+ mStartTimeMillis = nowMillis;
+ mPauseEndTimeMillis = 0;
mHandler.postAtFrontOfQueue(this);
}
@@ -360,20 +360,19 @@ public class Watchdog implements Dumpable {
}
/**
- * Sets the timeout of the HandlerChecker for one run.
- *
- * <p>The current run will be ignored and the next run will be set to this timeout.
+ * Pauses the checks for the given time.
*
- * <p>If a one off timeout is already set, the maximum timeout will be used.
+ * <p>The current run will be ignored and another run will be scheduled after
+ * the given time.
*/
- public void setOneOffTimeoutLocked(int temporaryTimeoutMillis, String reason) {
- mOneOffTimeoutMillis = Math.max(temporaryTimeoutMillis, mOneOffTimeoutMillis);
+ public void pauseForLocked(int pauseMillis, String reason) {
+ mPauseEndTimeMillis = SystemClock.uptimeMillis() + pauseMillis;
// Mark as completed, because there's a chance we called this after the watchog
// thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
// the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
mCompleted = true;
- Slog.i(TAG, "Extending timeout of HandlerChecker: " + mName + " for reason: "
- + reason + ". New timeout: " + mOneOffTimeoutMillis);
+ Slog.i(TAG, "Pausing of HandlerChecker: " + mName + " for reason: "
+ + reason + ". Pause end time: " + mPauseEndTimeMillis);
}
/** Pause the HandlerChecker. */
@@ -623,34 +622,32 @@ public class Watchdog implements Dumpable {
}
/**
- * Sets a one-off timeout for the next run of the watchdog for this thread. This is useful
+ * Pauses the checks of the watchdog for this thread. This is useful
* to run a slow operation on one of the monitored thread.
*
- * <p>After the next run, the timeout will go back to the default value.
- *
- * <p>If the current thread has not been added to the Watchdog, this call is a no-op.
- *
- * <p>If a one-off timeout for the current thread is already, the max value will be used.
+ * <p>After the given time, the timeout will go back to the default value.
+ * <p>This method does not require resume to be called.
*/
- public void setOneOffTimeoutForCurrentThread(int oneOffTimeoutMillis, String reason) {
+ public void pauseWatchingCurrentThreadFor(int pauseMillis, String reason) {
synchronized (mLock) {
for (HandlerCheckerAndTimeout hc : mHandlerCheckers) {
HandlerChecker checker = hc.checker();
if (Thread.currentThread().equals(checker.getThread())) {
- checker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+ checker.pauseForLocked(pauseMillis, reason);
}
}
}
}
/**
- * Sets a one-off timeout for the next run of the watchdog for the monitor thread.
+ * Pauses the checks of the watchdog for the monitor thread for the given time
*
- * <p>Simiar to {@link setOneOffTimeoutForCurrentThread} but used for monitors added through
- * {@link #addMonitor}
+ * <p>Similar to {@link pauseWatchingCurrentThreadFor} but used for monitors added
+ * through {@link #addMonitor}
+ * <p>This method does not require resume to be called.
*/
- public void setOneOffTimeoutForMonitors(int oneOffTimeoutMillis, String reason) {
- mMonitorChecker.setOneOffTimeoutLocked(oneOffTimeoutMillis, reason);
+ public void pauseWatchingMonitorsFor(int pauseMillis, String reason) {
+ mMonitorChecker.pauseForLocked(pauseMillis, reason);
}
/**
@@ -664,7 +661,7 @@ public class Watchdog implements Dumpable {
* adds another pause and will require an additional {@link #resumeCurrentThread} to resume.
*
* <p>Note: Use with care, as any deadlocks on the current thread will be undetected until all
- * pauses have been resumed. Prefer to use #setOneOffTimeoutForCurrentThread.
+ * pauses have been resumed. Prefer to use #pauseWatchingCurrentThreadFor.
*/
public void pauseWatchingCurrentThread(String reason) {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1650a96a4012..bdda95ef58c8 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -190,6 +190,7 @@ public class AccountManagerService
final MessageHandler mHandler;
+ private static final int TIMEOUT_DELAY_MS = 1000 * 60;
// Messages that can be sent on mHandler
private static final int MESSAGE_TIMED_OUT = 3;
private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
@@ -4942,6 +4943,7 @@ public class AccountManagerService
synchronized (mSessions) {
mSessions.put(toString(), this);
}
+ scheduleTimeout();
if (response != null) {
try {
response.asBinder().linkToDeath(this, 0 /* flags */);
@@ -5109,6 +5111,11 @@ public class AccountManagerService
}
}
+ private void scheduleTimeout() {
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
+ }
+
public void cancelTimeout() {
mHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
@@ -5146,6 +5153,9 @@ public class AccountManagerService
public void onTimedOut() {
IAccountManagerResponse response = getResponseAndClose();
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Session.onTimedOut");
+ }
if (response != null) {
try {
response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 31817f1c427d..ef67cbe5024b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -77,8 +77,10 @@ import static android.os.IServiceManager.DUMP_FLAG_PROTO;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerExemptionManager.REASON_ACTIVITY_VISIBILITY_GRACE_PERIOD;
import static android.os.PowerExemptionManager.REASON_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
+import static android.os.PowerExemptionManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_BTOP;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -4840,6 +4842,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!mConstants.mEnableWaitForFinishAttachApplication) {
finishAttachApplicationInner(startSeq, callingUid, pid);
}
+ maybeSendBootCompletedLocked(app);
} catch (Exception e) {
// We need kill the process group here. (b/148588589)
Slog.wtf(TAG, "Exception thrown during bind of " + app, e);
@@ -5066,6 +5069,45 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /**
+ * Send LOCKED_BOOT_COMPLETED and BOOT_COMPLETED to the package explicitly when unstopped
+ */
+ private void maybeSendBootCompletedLocked(ProcessRecord app) {
+ // Nothing to do if it wasn't previously stopped
+ if (!android.content.pm.Flags.stayStopped() || !app.wasForceStopped()) return;
+
+ // Send LOCKED_BOOT_COMPLETED, if necessary
+ if (app.getApplicationInfo().isEncryptionAware()) {
+ sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED),
+ REASON_LOCKED_BOOT_COMPLETED);
+ }
+ // Send BOOT_COMPLETED if the user is unlocked
+ if (StorageManager.isUserKeyUnlocked(app.userId)) {
+ sendBootBroadcastToAppLocked(app, new Intent(Intent.ACTION_BOOT_COMPLETED),
+ REASON_BOOT_COMPLETED);
+ }
+ app.setWasForceStopped(false);
+ }
+
+ /** Send a boot_completed broadcast to app */
+ private void sendBootBroadcastToAppLocked(ProcessRecord app, Intent intent,
+ @PowerExemptionManager.ReasonCode int reason) {
+ intent.setPackage(app.info.packageName);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, app.userId);
+ intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+ final BroadcastOptions bOptions = mUserController.getTemporaryAppAllowlistBroadcastOptions(
+ reason);
+
+ broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
+ new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+ null, null, AppOpsManager.OP_NONE,
+ bOptions.toBundle(), true,
+ false, MY_PID, SYSTEM_UID,
+ SYSTEM_UID, MY_PID, app.userId);
+ }
+
@Override
public void showBootMessage(final CharSequence msg, final boolean always) {
if (Binder.getCallingUid() != myUid()) {
@@ -5476,7 +5518,7 @@ public class ActivityManagerService extends IActivityManager.Stub
+ " Calling package: " + packageName + "; intent: " + intent
+ "; options: " + options);
}
- target.send(code, intent, resolvedType, allowlistToken, null,
+ target.send(code, intent, resolvedType, null, null,
requiredPermission, options);
} catch (RemoteException e) {
}
diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
index 9b5f18caf71a..710278d6b3c6 100644
--- a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
+++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
@@ -54,6 +56,7 @@ final class AppWaitingForDebuggerDialog extends BaseErrorDialog {
setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app));
setTitle("Waiting For Debugger");
WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ attrs.privateFlags |= SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Waiting For Debugger: " + app.info.processName);
getWindow().setAttributes(attrs);
}
diff --git a/services/core/java/com/android/server/am/LowMemDetector.java b/services/core/java/com/android/server/am/LowMemDetector.java
index 8f791331f0cf..016d3cddae31 100644
--- a/services/core/java/com/android/server/am/LowMemDetector.java
+++ b/services/core/java/com/android/server/am/LowMemDetector.java
@@ -23,6 +23,7 @@ import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NOR
import static com.android.internal.app.procstats.ProcessStats.ADJ_NOTHING;
import android.annotation.IntDef;
+import android.os.Trace;
import com.android.internal.annotations.GuardedBy;
@@ -90,17 +91,31 @@ public final class LowMemDetector {
private native int waitForPressure();
private final class LowMemThread extends Thread {
+ private boolean mIsTracingMemCriticalLow;
+
+ LowMemThread() {
+ super("LowMemThread");
+ }
+
public void run() {
while (true) {
// sleep waiting for a PSI event
int newPressureState = waitForPressure();
+ // PSI event detected
+ boolean isCriticalLowMemory = newPressureState == ADJ_MEM_FACTOR_CRITICAL;
+ if (isCriticalLowMemory && !mIsTracingMemCriticalLow) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "criticalLowMemory");
+ } else if (!isCriticalLowMemory && mIsTracingMemCriticalLow) {
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+ mIsTracingMemCriticalLow = isCriticalLowMemory;
if (newPressureState == -1) {
// epoll broke, tear this down
mAvailable = false;
break;
}
- // got a PSI event? let's update lowmem info
+ // got an actual PSI event? let's update lowmem info
synchronized (mPressureStateLock) {
mPressureState = newPressureState;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index e0e6cade5f27..f04198ed39ec 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -869,6 +869,8 @@ public final class ProcessList {
ApplicationExitInfo.REASON_LOW_MEMORY,
ApplicationExitInfo.SUBREASON_OOM_KILL,
"oom");
+
+ oomKill.logKillOccurred();
}
}
}
@@ -3255,6 +3257,17 @@ public final class ProcessList {
hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName());
final ProcessStateRecord state = r.mState;
+ // Check if we should mark the processrecord for first launch after force-stopping
+ if ((r.getApplicationInfo().flags & ApplicationInfo.FLAG_STOPPED) != 0) {
+ final boolean wasPackageEverLaunched = mService.getPackageManagerInternal()
+ .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId);
+ // If the package was launched in the past but is currently stopped, only then it
+ // should be considered as stopped after use. Do not mark it if it's the first launch.
+ if (wasPackageEverLaunched) {
+ r.setWasForceStopped(true);
+ }
+ }
+
if (!isolated && !isSdkSandbox
&& userId == UserHandle.USER_SYSTEM
&& (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 940c58b7a5f0..354f3d3d13e0 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -23,6 +23,7 @@ import android.app.IApplicationThread;
import android.app.ProcessMemoryState.HostingComponentType;
import android.content.pm.ApplicationInfo;
import android.os.Debug;
+import android.os.Process;
import android.os.SystemClock;
import android.util.DebugUtils;
import android.util.TimeUtils;
@@ -271,15 +272,17 @@ final class ProcessProfileRecord {
origBase.makeInactive();
}
final ApplicationInfo info = mApp.info;
+ final int attributionUid = getUidForAttribution(mApp);
final ProcessState baseProcessTracker = tracker.getProcessStateLocked(
- info.packageName, info.uid, info.longVersionCode, mApp.processName);
+ info.packageName, attributionUid, info.longVersionCode,
+ mApp.processName);
setBaseProcessTracker(baseProcessTracker);
baseProcessTracker.makeActive();
pkgList.forEachPackage((pkgName, holder) -> {
if (holder.state != null && holder.state != origBase) {
holder.state.makeInactive();
}
- tracker.updateProcessStateHolderLocked(holder, pkgName, mApp.info.uid,
+ tracker.updateProcessStateHolderLocked(holder, pkgName, attributionUid,
mApp.info.longVersionCode, mApp.processName);
if (holder.state != baseProcessTracker) {
holder.state.makeActive();
@@ -536,7 +539,7 @@ final class ProcessProfileRecord {
tracker.reportCachedKill(pkgList.getPackageListLocked(), mLastCachedPss);
pkgList.forEachPackageProcessStats(holder ->
FrameworkStatsLog.write(FrameworkStatsLog.CACHED_KILL_REPORTED,
- mApp.info.uid,
+ getUidForAttribution(mApp),
holder.state.getName(),
holder.state.getPackage(),
mLastCachedPss,
@@ -595,6 +598,21 @@ final class ProcessProfileRecord {
tracker.mPendingMemState = -1;
}
+ /**
+ * Returns the uid that should be used for attribution purposes in profiling / stats.
+ *
+ * In most cases this returns the uid of the process itself. For isolated processes though,
+ * since the process uid is dynamically allocated and can't easily be traced back to the app,
+ * for attribution we use the app package uid.
+ */
+ private static int getUidForAttribution(ProcessRecord processRecord) {
+ if (Process.isIsolatedUid(processRecord.uid)) {
+ return processRecord.info.uid;
+ } else {
+ return processRecord.uid;
+ }
+ }
+
@GuardedBy("mProfilerLock")
int getPid() {
return mPid;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f02b8c737f90..2c6e598ef0a4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -438,6 +438,9 @@ class ProcessRecord implements WindowProcessListener {
final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
+ /** Whether the app was launched from a stopped state and is being unstopped. */
+ volatile boolean mWasForceStopped;
+
void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
long startUptime, long startElapsedTime) {
this.mStartUid = startUid;
@@ -1602,4 +1605,12 @@ class ProcessRecord implements WindowProcessListener {
List<ProcessRecord> getLruProcessList() {
return mService.mProcessList.getLruProcessesLOSP();
}
+
+ public void setWasForceStopped(boolean stopped) {
+ mWasForceStopped = stopped;
+ }
+
+ public boolean wasForceStopped() {
+ return mWasForceStopped;
+ }
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index a165e8897aa4..f5f2b102596b 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -347,8 +347,10 @@ final class ProcessServiceRecord {
mHasAboveClient = false;
for (int i = mConnections.size() - 1; i >= 0; i--) {
ConnectionRecord cr = mConnections.valueAt(i);
- if (cr.binding.service.app.mServices != this
- && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
+
+ final boolean isSameProcess = cr.binding.service.app != null
+ && cr.binding.service.app.mServices == this;
+ if (!isSameProcess && cr.hasFlag(Context.BIND_ABOVE_CLIENT)) {
mHasAboveClient = true;
break;
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 2d231b3cf42e..a57a785153e0 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -118,6 +118,7 @@ public class SettingsToPropertiesMapper {
// The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
+ "accessibility",
"android_core_networking",
"angle",
"arc_next",
@@ -144,6 +145,7 @@ public class SettingsToPropertiesMapper {
"media_drm",
"media_solutions",
"nfc",
+ "pdf_viewer",
"pixel_audio_android",
"pixel_system_sw_touch",
"pixel_watch",
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index de4ad20bdccc..0dd579fd0b15 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -3387,7 +3387,7 @@ class UserController implements Handler.Callback {
}
- private BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
+ BroadcastOptions getTemporaryAppAllowlistBroadcastOptions(
@PowerWhitelistManager.ReasonCode int reasonCode) {
long duration = 10_000;
final ActivityManagerInternal amInternal =
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index bb9ea285385b..cbaf05bb4e23 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -15,3 +15,10 @@ flag {
description: "Feature flag for the ANR timer service"
bug: "282428924"
}
+
+flag {
+ name: "fgs_abuse_detection"
+ namespace: "backstage_power"
+ description: "Detect abusive FGS behavior for certain types (camera, mic, media, location)."
+ bug: "295545575"
+}
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index ba43c8df92c5..292fc14ac6eb 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -188,7 +188,7 @@ import java.util.Objects;
* {@link AdiDeviceState#toPersistableString()}.
*/
public static int getPeristedMaxSize() {
- return 36; /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ return 36; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1
+ (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
+ (SETTINGS_FIELD_SEPARATOR)5 */
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 35260ed6f148..7abd9c7f750b 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -39,10 +39,9 @@ import android.media.ISpatializerHeadTrackingCallback;
import android.media.ISpatializerHeadTrackingModeCallback;
import android.media.ISpatializerOutputCallback;
import android.media.MediaMetrics;
-import android.media.SpatializationLevel;
-import android.media.SpatializationMode;
import android.media.Spatializer;
-import android.media.SpatializerHeadTrackingMode;
+import android.media.audio.common.HeadTracking;
+import android.media.audio.common.Spatialization;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -84,22 +83,22 @@ public class SpatializerHelper {
/*package*/ static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
{
- append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL);
+ append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_WIRED_HEADSET, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, Spatialization.Mode.BINAURAL);
// assumption for A2DP: mostly headsets
- append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
- append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
- append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_DOCK, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_USB_ACCESSORY, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_USB_DEVICE, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_USB_HEADSET, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_LINE_ANALOG, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_LINE_DIGITAL, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_AUX_LINE, Spatialization.Mode.TRANSAURAL);
+ append(AudioDeviceInfo.TYPE_BLE_HEADSET, Spatialization.Mode.BINAURAL);
+ append(AudioDeviceInfo.TYPE_BLE_SPEAKER, Spatialization.Mode.TRANSAURAL);
// assumption that BLE broadcast would be mostly consumed on headsets
- append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL);
+ append(AudioDeviceInfo.TYPE_BLE_BROADCAST, Spatialization.Mode.BINAURAL);
}
};
@@ -226,12 +225,12 @@ public class SpatializerHelper {
ArrayList<Integer> list = new ArrayList<>(0);
for (byte value : values) {
switch (value) {
- case SpatializerHeadTrackingMode.OTHER:
- case SpatializerHeadTrackingMode.DISABLED:
+ case HeadTracking.Mode.OTHER:
+ case HeadTracking.Mode.DISABLED:
// not expected here, skip
break;
- case SpatializerHeadTrackingMode.RELATIVE_WORLD:
- case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ case HeadTracking.Mode.RELATIVE_WORLD:
+ case HeadTracking.Mode.RELATIVE_SCREEN:
list.add(headTrackingModeTypeToSpatializerInt(value));
break;
default:
@@ -254,10 +253,10 @@ public class SpatializerHelper {
byte[] spatModes = spat.getSupportedModes();
for (byte mode : spatModes) {
switch (mode) {
- case SpatializationMode.SPATIALIZER_BINAURAL:
+ case Spatialization.Mode.BINAURAL:
mBinauralSupported = true;
break;
- case SpatializationMode.SPATIALIZER_TRANSAURAL:
+ case Spatialization.Mode.TRANSAURAL:
mTransauralSupported = true;
break;
default:
@@ -274,8 +273,8 @@ public class SpatializerHelper {
// initialize list of compatible devices
for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) {
int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i);
- if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
- || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL
+ if ((mode == (int) Spatialization.Mode.BINAURAL && mBinauralSupported)
+ || (mode == (int) Spatialization.Mode.TRANSAURAL
&& mTransauralSupported)) {
mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
}
@@ -577,9 +576,9 @@ public class SpatializerHelper {
int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(device.getDeviceType(),
Integer.MIN_VALUE);
- device.setSAEnabled(spatMode == SpatializationMode.SPATIALIZER_BINAURAL
+ device.setSAEnabled(spatMode == Spatialization.Mode.BINAURAL
? mBinauralEnabledDefault
- : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL
+ : spatMode == Spatialization.Mode.TRANSAURAL
? mTransauralEnabledDefault
: false);
device.setHeadTrackerEnabled(mHeadTrackingEnabledDefault);
@@ -629,9 +628,9 @@ public class SpatializerHelper {
if (isBluetoothDevice(internalDeviceType)) return deviceType;
final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
- if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+ if (spatMode == Spatialization.Mode.TRANSAURAL) {
return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
- } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+ } else if (spatMode == Spatialization.Mode.BINAURAL) {
return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
}
return AudioDeviceInfo.TYPE_UNKNOWN;
@@ -690,8 +689,7 @@ public class SpatializerHelper {
// since their physical characteristics are unknown
if (deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_UNKNOWN
|| deviceState.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES) {
- available = (spatMode == SpatializationMode.SPATIALIZER_BINAURAL)
- && mBinauralSupported;
+ available = (spatMode == Spatialization.Mode.BINAURAL) && mBinauralSupported;
} else {
available = false;
}
@@ -804,8 +802,8 @@ public class SpatializerHelper {
// not be included.
final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
/*default when type not found*/ -1);
- if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
- || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
+ if ((modeForDevice == Spatialization.Mode.BINAURAL && mBinauralSupported)
+ || (modeForDevice == Spatialization.Mode.TRANSAURAL
&& mTransauralSupported)) {
return true;
}
@@ -1479,7 +1477,7 @@ public class SpatializerHelper {
}
synchronized void onInitSensors() {
- final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE);
+ final boolean init = mFeatureEnabled && (mSpatLevel != Spatialization.Level.NONE);
final String action = init ? "initializing" : "releasing";
if (mSpat == null) {
logloge("not " + action + " sensors, null spatializer");
@@ -1545,13 +1543,13 @@ public class SpatializerHelper {
// SDK <-> AIDL converters
private static int headTrackingModeTypeToSpatializerInt(byte mode) {
switch (mode) {
- case SpatializerHeadTrackingMode.OTHER:
+ case HeadTracking.Mode.OTHER:
return Spatializer.HEAD_TRACKING_MODE_OTHER;
- case SpatializerHeadTrackingMode.DISABLED:
+ case HeadTracking.Mode.DISABLED:
return Spatializer.HEAD_TRACKING_MODE_DISABLED;
- case SpatializerHeadTrackingMode.RELATIVE_WORLD:
+ case HeadTracking.Mode.RELATIVE_WORLD:
return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
- case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
+ case HeadTracking.Mode.RELATIVE_SCREEN:
return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
default:
throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode));
@@ -1561,13 +1559,13 @@ public class SpatializerHelper {
private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
switch (sdkMode) {
case Spatializer.HEAD_TRACKING_MODE_OTHER:
- return SpatializerHeadTrackingMode.OTHER;
+ return HeadTracking.Mode.OTHER;
case Spatializer.HEAD_TRACKING_MODE_DISABLED:
- return SpatializerHeadTrackingMode.DISABLED;
+ return HeadTracking.Mode.DISABLED;
case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
- return SpatializerHeadTrackingMode.RELATIVE_WORLD;
+ return HeadTracking.Mode.RELATIVE_WORLD;
case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
- return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
+ return HeadTracking.Mode.RELATIVE_SCREEN;
default:
throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
}
@@ -1575,11 +1573,11 @@ public class SpatializerHelper {
private static int spatializationLevelToSpatializerInt(byte level) {
switch (level) {
- case SpatializationLevel.NONE:
+ case Spatialization.Level.NONE:
return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
- case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
+ case Spatialization.Level.MULTICHANNEL:
return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
- case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
+ case Spatialization.Level.BED_PLUS_OBJECTS:
return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
default:
throw (new IllegalArgumentException("Unexpected spatializer level:" + level));
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 3d347bea6bae..f9bc8dcc7d0b 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -53,6 +53,8 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.media.AudioManager;
import android.nfc.INfcAdapter;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -163,10 +165,6 @@ public class CameraServiceProxy extends SystemService
* SCALER_ROTATE_AND_CROP_NONE -> Always return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE
*/
- // Flags arguments to NFC adapter to enable/disable NFC
- public static final int DISABLE_POLLING_FLAGS = 0x1000;
- public static final int ENABLE_POLLING_FLAGS = 0x0000;
-
// Handler message codes
private static final int MSG_SWITCH_USER = 1;
private static final int MSG_NOTIFY_DEVICE_STATE = 2;
@@ -216,7 +214,6 @@ public class CameraServiceProxy extends SystemService
private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
- private static final String NFC_SERVICE_BINDER_NAME = "nfc";
private static final IBinder nfcInterfaceToken = new Binder();
private final boolean mNotifyNfc;
@@ -1274,8 +1271,13 @@ public class CameraServiceProxy extends SystemService
}
}
- private void notifyNfcService(boolean enablePolling) {
-
+ // TODO(b/303286040): Remove the raw INfcAdapter usage once |ENABLE_NFC_MAINLINE_FLAG| is
+ // rolled out.
+ private static final String NFC_SERVICE_BINDER_NAME = "nfc";
+ // Flags arguments to NFC adapter to enable/disable NFC
+ public static final int DISABLE_POLLING_FLAGS = 0x1000;
+ public static final int ENABLE_POLLING_FLAGS = 0x0000;
+ private void setNfcReaderModeUsingINfcAdapter(boolean enablePolling) {
IBinder nfcServiceBinder = getBinderService(NFC_SERVICE_BINDER_NAME);
if (nfcServiceBinder == null) {
Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
@@ -1291,6 +1293,25 @@ public class CameraServiceProxy extends SystemService
}
}
+ private void notifyNfcService(boolean enablePolling) {
+ if (android.nfc.Flags.enableNfcMainline()) {
+ NfcManager nfcManager = mContext.getSystemService(NfcManager.class);
+ if (nfcManager == null) {
+ Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+ return;
+ }
+ NfcAdapter nfcAdapter = nfcManager.getDefaultAdapter();
+ if (nfcAdapter == null) {
+ Slog.w(TAG, "Could not connect to NFC service to notify it of camera state");
+ return;
+ }
+ if (DEBUG) Slog.v(TAG, "Setting NFC reader mode. enablePolling: " + enablePolling);
+ nfcAdapter.setReaderMode(enablePolling);
+ } else {
+ setNfcReaderModeUsingINfcAdapter(enablePolling);
+ }
+ }
+
private static int[] toArray(Collection<Integer> c) {
int len = c.size();
int[] ret = new int[len];
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 4f1df3fb996e..70d4ad2c6e1f 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,14 +120,14 @@ abstract class DisplayAdapter {
}
public static Display.Mode createMode(int width, int height, float refreshRate) {
- return createMode(width, height, refreshRate, new float[0], new int[0]);
+ return createMode(width, height, refreshRate, refreshRate, new float[0], new int[0]);
}
- public static Display.Mode createMode(int width, int height, float refreshRate,
+ public static Display.Mode createMode(int width, int height, float refreshRate, float vsyncRate,
float[] alternativeRefreshRates,
@Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
- alternativeRefreshRates, supportedHdrTypes);
+ vsyncRate, alternativeRefreshRates, supportedHdrTypes);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index 425a1af1bbaa..669580189d7c 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -27,6 +27,8 @@ import java.util.Objects;
* the DisplayBrightnessModeStrategies when updating the brightness.
*/
public final class DisplayBrightnessState {
+ public static final float CUSTOM_ANIMATION_RATE_NOT_SET = -1f;
+
private final float mBrightness;
private final float mSdrBrightness;
@@ -37,6 +39,8 @@ public final class DisplayBrightnessState {
private final boolean mIsSlowChange;
+ private final float mCustomAnimationRate;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mSdrBrightness = builder.getSdrBrightness();
@@ -45,6 +49,7 @@ public final class DisplayBrightnessState {
mShouldUseAutoBrightness = builder.getShouldUseAutoBrightness();
mIsSlowChange = builder.isSlowChange();
mMaxBrightness = builder.getMaxBrightness();
+ mCustomAnimationRate = builder.getCustomAnimationRate();
}
/**
@@ -97,7 +102,12 @@ public final class DisplayBrightnessState {
return mMaxBrightness;
}
-
+ /**
+ * @return custom animation rate
+ */
+ public float getCustomAnimationRate() {
+ return mCustomAnimationRate;
+ }
@Override
public String toString() {
@@ -112,6 +122,7 @@ public final class DisplayBrightnessState {
stringBuilder.append(getShouldUseAutoBrightness());
stringBuilder.append("\n isSlowChange:").append(mIsSlowChange);
stringBuilder.append("\n maxBrightness:").append(mMaxBrightness);
+ stringBuilder.append("\n customAnimationRate:").append(mCustomAnimationRate);
return stringBuilder.toString();
}
@@ -137,13 +148,14 @@ public final class DisplayBrightnessState {
otherState.getDisplayBrightnessStrategyName())
&& mShouldUseAutoBrightness == otherState.getShouldUseAutoBrightness()
&& mIsSlowChange == otherState.isSlowChange()
- && mMaxBrightness == otherState.getMaxBrightness();
+ && mMaxBrightness == otherState.getMaxBrightness()
+ && mCustomAnimationRate == otherState.getCustomAnimationRate();
}
@Override
public int hashCode() {
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
- mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness);
+ mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mCustomAnimationRate);
}
/**
@@ -164,6 +176,7 @@ public final class DisplayBrightnessState {
private boolean mShouldUseAutoBrightness;
private boolean mIsSlowChange;
private float mMaxBrightness;
+ private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET;
/**
* Create a builder starting with the values from the specified {@link
@@ -180,6 +193,7 @@ public final class DisplayBrightnessState {
builder.setShouldUseAutoBrightness(state.getShouldUseAutoBrightness());
builder.setIsSlowChange(state.isSlowChange());
builder.setMaxBrightness(state.getMaxBrightness());
+ builder.setCustomAnimationRate(state.getCustomAnimationRate());
return builder;
}
@@ -303,6 +317,22 @@ public final class DisplayBrightnessState {
return mMaxBrightness;
}
+
+ /**
+ * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+ */
+ public Builder setCustomAnimationRate(float animationRate) {
+ this.mCustomAnimationRate = animationRate;
+ return this;
+ }
+
+ /**
+ * See {@link DisplayBrightnessState#getCustomAnimationRate()}.
+ */
+ public float getCustomAnimationRate() {
+ return mCustomAnimationRate;
+ }
+
/**
* This is used to construct an immutable DisplayBrightnessState object from its builder
*/
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index cb2302a60248..57b2c24a8b88 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -586,7 +586,8 @@ public final class DisplayManagerService extends SystemService {
mSystemReady = false;
mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
mExtraDisplayLoggingPackageName = DisplayProperties.debug_vri_package().orElse(null);
- mExtraDisplayEventLogging = !TextUtils.isEmpty(mExtraDisplayLoggingPackageName);
+ // TODO: b/306170135 - return TextUtils package name check instead
+ mExtraDisplayEventLogging = true;
}
public void setupSchedulerPolicies() {
@@ -1131,6 +1132,7 @@ public final class DisplayManagerService extends SystemService {
new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
overriddenInfo.refreshRateOverride,
+ currentMode.getVsyncRate(),
new float[0], currentMode.getSupportedHdrTypes());
overriddenInfo.modeId =
overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
@@ -2306,8 +2308,10 @@ public final class DisplayManagerService extends SystemService {
@GuardedBy("mSyncRoot")
private boolean hdrConversionIntroducesLatencyLocked() {
+ HdrConversionMode mode = getHdrConversionModeSettingInternal();
final int preferredHdrOutputType =
- getHdrConversionModeSettingInternal().getPreferredHdrOutputType();
+ mode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM
+ ? mSystemPreferredHdrOutputType : mode.getPreferredHdrOutputType();
if (preferredHdrOutputType != Display.HdrCapabilities.HDR_TYPE_INVALID) {
int[] hdrTypesWithLatency = mInjector.getHdrOutputTypesWithLatency();
return ArrayUtils.contains(hdrTypesWithLatency, preferredHdrOutputType);
@@ -2588,16 +2592,14 @@ public final class DisplayManagerService extends SystemService {
// TODO(b/202378408) set minimal post-processing only if it's supported once we have a
// separate API for disabling on-device processing.
boolean mppRequest = isMinimalPostProcessingAllowed() && preferMinimalPostProcessing;
- boolean disableHdrConversionForLatency = false;
+ // If HDR conversion introduces latency, disable that in case minimal
+ // post-processing is requested
+ boolean disableHdrConversionForLatency =
+ mppRequest ? hdrConversionIntroducesLatencyLocked() : false;
if (display.getRequestedMinimalPostProcessingLocked() != mppRequest) {
display.setRequestedMinimalPostProcessingLocked(mppRequest);
shouldScheduleTraversal = true;
- // If HDR conversion introduces latency, disable that in case minimal
- // post-processing is requested
- if (mppRequest) {
- disableHdrConversionForLatency = hdrConversionIntroducesLatencyLocked();
- }
}
if (shouldScheduleTraversal) {
@@ -2933,8 +2935,15 @@ public final class DisplayManagerService extends SystemService {
// Send a display event if the display is enabled
private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display,
@DisplayEvent int event) {
+ final boolean displayIsEnabled = display.isEnabledLocked();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "sendDisplayEventLocked#event=" + event + ",displayEnabled="
+ + displayIsEnabled);
+ }
+
// Only send updates outside of DisplayManagerService for enabled displays
- if (display.isEnabledLocked()) {
+ if (displayIsEnabled) {
sendDisplayEventLocked(display, event);
} else if (mExtraDisplayEventLogging) {
Slog.i(TAG, "Not Sending Display Event; display is not enabled: " + display);
@@ -2991,7 +3000,11 @@ public final class DisplayManagerService extends SystemService {
+ displayId + ", event=" + event
+ (uids != null ? ", uids=" + uids : ""));
}
-
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "deliverDisplayEvent#event=" + event + ",displayId="
+ + displayId + (uids != null ? ", uids=" + uids : ""));
+ }
// Grab the lock and copy the callbacks.
final int count;
synchronized (mSyncRoot) {
@@ -3031,7 +3044,8 @@ public final class DisplayManagerService extends SystemService {
}
private boolean extraLogging(String packageName) {
- return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
+ // TODO: b/306170135 - return mExtraDisplayLoggingPackageName & package name check instead
+ return true;
}
// Runs on Handler thread.
@@ -3498,10 +3512,13 @@ public final class DisplayManagerService extends SystemService {
@Override
public void binderDied() {
- if (DEBUG || mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
- mPackageName)) {
+ if (DEBUG || extraLogging(mPackageName)) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "displayManagerBinderDied#mPid=" + mPid);
+ }
onCallbackDied(this);
}
@@ -3510,11 +3527,15 @@ public final class DisplayManagerService extends SystemService {
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
- if (mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(
- mPackageName)) {
+ if (extraLogging(mPackageName)) {
Slog.i(TAG,
"Not sending displayEvent: " + event + " due to mask:" + mEventsMask);
}
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
+ Trace.instant(Trace.TRACE_TAG_POWER,
+ "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsMask="
+ + mEventsMask);
+ }
return true;
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index d97c8e71c73c..8c39d7de54f7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -16,6 +16,13 @@
package com.android.server.display;
+import static android.view.Display.TYPE_EXTERNAL;
+import static android.view.Display.TYPE_INTERNAL;
+import static android.view.Display.TYPE_OVERLAY;
+import static android.view.Display.TYPE_UNKNOWN;
+import static android.view.Display.TYPE_VIRTUAL;
+import static android.view.Display.TYPE_WIFI;
+
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
@@ -26,7 +33,10 @@ import android.view.Display;
import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
@@ -153,9 +163,12 @@ class DisplayManagerShellCommand extends ShellCommand {
pw.println(" Sets the user disabled HDR types as TYPES");
pw.println(" get-user-disabled-hdr-types");
pw.println(" Returns the user disabled HDR types");
- pw.println(" get-displays [CATEGORY]");
+ pw.println(" get-displays [-c|--category CATEGORY] [-i|--ids-only] [-t|--type TYPE]");
+ pw.println(" [CATEGORY]");
pw.println(" Returns the current displays. Can specify string category among");
pw.println(" DisplayManager.DISPLAY_CATEGORY_*; must use the actual string value.");
+ pw.println(" Can choose to print only the ids of the displays. " + "Can filter by");
+ pw.println(" display types. For example, '--type external'");
pw.println(" dock");
pw.println(" Sets brightness to docked + idle screen brightness mode");
pw.println(" undock");
@@ -171,17 +184,94 @@ class DisplayManagerShellCommand extends ShellCommand {
}
private int getDisplays() {
- String category = getNextArg();
+ String opt = "", requestedType, category = null;
+ PrintWriter out = getOutPrintWriter();
+
+ List<Integer> displayTypeList = new ArrayList<>();
+ boolean showIdsOnly = false, filterByType = false;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-i":
+ case "--ids-only":
+ showIdsOnly = true;
+ break;
+ case "-t":
+ case "--type":
+ requestedType = getNextArgRequired();
+ int displayType = getType(requestedType, out);
+ if (displayType == -1) {
+ return 1;
+ }
+ displayTypeList.add(displayType);
+ filterByType = true;
+ break;
+ case "-c":
+ case "--category":
+ if (category != null) {
+ out.println("Error: the category has been specified more than one time. "
+ + "Please select only one category.");
+ return 1;
+ }
+ category = getNextArgRequired();
+ break;
+ case "":
+ break;
+ default:
+ out.println("Error: unknown option '" + opt + "'");
+ return 1;
+ }
+ }
+
+ String lastCategoryArgument = getNextArg();
+ if (lastCategoryArgument != null) {
+ if (category != null) {
+ out.println("Error: the category has been specified both with the -c option and "
+ + "the positional argument. Please select only one category.");
+ return 1;
+ }
+ category = lastCategoryArgument;
+ }
+
DisplayManager dm = mService.getContext().getSystemService(DisplayManager.class);
Display[] displays = dm.getDisplays(category);
- PrintWriter out = getOutPrintWriter();
- out.println("Displays:");
+
+ if (filterByType) {
+ displays = Arrays.stream(displays).filter(d -> displayTypeList.contains(d.getType()))
+ .toArray(Display[]::new);
+ }
+
+ if (!showIdsOnly) {
+ out.println("Displays:");
+ }
for (int i = 0; i < displays.length; i++) {
- out.println(" " + displays[i]);
+ out.println((showIdsOnly ? displays[i].getDisplayId() : displays[i]));
}
return 0;
}
+ private int getType(String type, PrintWriter out) {
+ type = type.toUpperCase(Locale.ENGLISH);
+ switch (type) {
+ case "UNKNOWN":
+ return TYPE_UNKNOWN;
+ case "INTERNAL":
+ return TYPE_INTERNAL;
+ case "EXTERNAL":
+ return TYPE_EXTERNAL;
+ case "WIFI":
+ return TYPE_WIFI;
+ case "OVERLAY":
+ return TYPE_OVERLAY;
+ case "VIRTUAL":
+ return TYPE_VIRTUAL;
+ default:
+ out.println("Error: argument for display type should be "
+ + "one of 'UNKNOWN', 'INTERNAL', 'EXTERNAL', 'WIFI', 'OVERLAY', 'VIRTUAL', "
+ + "but got '" + type + "' instead.");
+ return -1;
+ }
+ }
+
private int showNotification() {
final String notificationType = getNextArg();
if (notificationType == null) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 7d9c0182a691..0f0002781f93 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -561,8 +561,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
brightnessSetting, () -> postBrightnessChangeRunnable(),
new HandlerExecutor(mHandler));
- mBrightnessClamperController = new BrightnessClamperController(mHandler,
- modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
+ mBrightnessClamperController = mInjector.getBrightnessClamperController(
+ mHandler, modeChangeCallback::run,
+ new BrightnessClamperController.DisplayDeviceData(
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
logicalDisplay.getPowerThrottlingDataIdLocked(),
@@ -1353,6 +1354,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
float rawBrightnessState = displayBrightnessState.getBrightness();
mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
boolean slowChange = displayBrightnessState.isSlowChange();
+ // custom transition duration
+ float customAnimationRate = displayBrightnessState.getCustomAnimationRate();
// Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor
// doesn't yet have a valid lux value to use with auto-brightness.
@@ -1485,6 +1488,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
brightnessState = clampedState.getBrightness();
slowChange = clampedState.isSlowChange();
+ // faster rate wins, at this point customAnimationRate == -1, strategy does not control
+ // customAnimationRate. Should be revisited if strategy start setting this value
+ customAnimationRate = Math.max(customAnimationRate, clampedState.getCustomAnimationRate());
mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
if (updateScreenBrightnessSetting) {
@@ -1553,9 +1559,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
- // custom transition duration
- float customTransitionRate = -1f;
-
// If there are any HDR layers on the screen, we have a special brightness value that we
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1570,10 +1573,21 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
animateValue = mBrightnessRangeController.getHdrBrightnessValue();
- customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
+ customAnimationRate = Math.max(customAnimationRate,
+ mBrightnessRangeController.getHdrTransitionRate());
mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_HDR);
}
+ // if doze or suspend state is requested, we want to finish brightnes animation fast
+ // to allow state animation to start
+ if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+ && (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN // dozing
+ || mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
+ || mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
+ customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ slowChange = false;
+ }
+
final float currentBrightness = mPowerState.getScreenBrightness();
final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
@@ -1601,9 +1615,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (skipAnimation) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
- } else if (customTransitionRate > 0) {
+ } else if (customAnimationRate > 0) {
animateScreenBrightness(animateValue, sdrAnimateValue,
- customTransitionRate, /* ignoreAnimationLimits = */true);
+ customAnimationRate, /* ignoreAnimationLimits = */true);
} else {
boolean isIncreasing = animateValue > currentBrightness;
final float rampSpeed;
@@ -3059,6 +3073,15 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
modeChangeCallback, displayDeviceConfig, handler, flags, displayToken, info);
}
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+
+ return new BrightnessClamperController(handler, clamperChangeListener, data, context,
+ flags);
+ }
+
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return DisplayWhiteBalanceFactory.create(handler,
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index e5d38cb669d4..be3207dfb4ee 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -319,10 +319,10 @@ final class LocalDisplayAdapter extends DisplayAdapter {
SurfaceControl.DisplayMode other = displayModes[j];
boolean isAlternative = j != i && other.width == mode.width
&& other.height == mode.height
- && other.refreshRate != mode.refreshRate
+ && other.peakRefreshRate != mode.peakRefreshRate
&& other.group == mode.group;
if (isAlternative) {
- alternativeRefreshRates.add(displayModes[j].refreshRate);
+ alternativeRefreshRates.add(displayModes[j].peakRefreshRate);
}
}
Collections.sort(alternativeRefreshRates);
@@ -1360,7 +1360,7 @@ final class LocalDisplayAdapter extends DisplayAdapter {
DisplayModeRecord(SurfaceControl.DisplayMode mode,
float[] alternativeRefreshRates) {
- mMode = createMode(mode.width, mode.height, mode.refreshRate,
+ mMode = createMode(mode.width, mode.height, mode.peakRefreshRate, mode.vsyncRate,
alternativeRefreshRates, mode.supportedHdrTypes);
}
@@ -1375,7 +1375,9 @@ final class LocalDisplayAdapter extends DisplayAdapter {
return mMode.getPhysicalWidth() == mode.width
&& mMode.getPhysicalHeight() == mode.height
&& Float.floatToIntBits(mMode.getRefreshRate())
- == Float.floatToIntBits(mode.refreshRate);
+ == Float.floatToIntBits(mode.peakRefreshRate)
+ && Float.floatToIntBits(mMode.getVsyncRate())
+ == Float.floatToIntBits(mode.vsyncRate);
}
public String toString() {
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 68f72d3b085a..dfcda40d8e3c 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -19,6 +19,8 @@ package com.android.server.display.brightness.clamper;
import android.annotation.NonNull;
import android.os.PowerManager;
+import com.android.server.display.DisplayBrightnessState;
+
import java.io.PrintWriter;
/**
@@ -33,6 +35,10 @@ abstract class BrightnessClamper<T> {
return mBrightnessCap;
}
+ float getCustomAnimationRate() {
+ return DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
+ }
+
boolean isActive() {
return mIsActive;
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index 787f786a97da..b57491949196 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -51,6 +51,7 @@ import java.util.concurrent.Executor;
*/
public class BrightnessClamperController {
private static final String TAG = "BrightnessClamperController";
+
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
private final Handler mHandler;
private final ClamperChangeListener mClamperChangeListenerExternal;
@@ -60,6 +61,8 @@ public class BrightnessClamperController {
private final List<BrightnessModifier> mModifiers;
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener;
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
+
+ private float mCustomAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
@Nullable
private Type mClamperType = null;
private boolean mClamperApplied = false;
@@ -113,6 +116,7 @@ public class BrightnessClamperController {
builder.setIsSlowChange(slowChange);
builder.setBrightness(cappedBrightness);
builder.setMaxBrightness(mBrightnessCap);
+ builder.setCustomAnimationRate(mCustomAnimationRate);
if (mClamperType != null) {
builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
@@ -182,6 +186,7 @@ public class BrightnessClamperController {
private void recalculateBrightnessCap() {
float brightnessCap = PowerManager.BRIGHTNESS_MAX;
Type clamperType = null;
+ float customAnimationRate = DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET;
BrightnessClamper<?> minClamper = mClampers.stream()
.filter(BrightnessClamper::isActive)
@@ -191,11 +196,14 @@ public class BrightnessClamperController {
if (minClamper != null) {
brightnessCap = minClamper.getBrightnessCap();
clamperType = minClamper.getType();
+ customAnimationRate = minClamper.getCustomAnimationRate();
}
- if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
+ if (mBrightnessCap != brightnessCap || mClamperType != clamperType
+ || mCustomAnimationRate != customAnimationRate) {
mBrightnessCap = brightnessCap;
mClamperType = clamperType;
+ mCustomAnimationRate = customAnimationRate;
mClamperChangeListenerExternal.onChanged();
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 39f0b13f716a..200d88a78dd7 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -24,7 +24,6 @@ import android.os.PowerManager;
import android.view.SurfaceControlHdrLayerInfoListener;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.BrightnessUtils;
import com.android.server.display.config.HdrBrightnessData;
import java.io.PrintWriter;
@@ -176,21 +175,14 @@ public class HdrClamper {
} else if (mDesiredMaxBrightness != expectedMaxBrightness) {
mDesiredMaxBrightness = expectedMaxBrightness;
long debounceTime;
- long transitionDuration;
if (mDesiredMaxBrightness > mMaxBrightness) {
debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
- transitionDuration = mHdrBrightnessData.mBrightnessIncreaseDurationMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
} else {
debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
- transitionDuration = mHdrBrightnessData.mBrightnessDecreaseDurationMillis;
+ mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
}
- float maxHlg = BrightnessUtils.convertLinearToGamma(mMaxBrightness);
- float desiredMaxHlg = BrightnessUtils.convertLinearToGamma(mDesiredMaxBrightness);
-
- mDesiredTransitionRate = Math.abs(
- (maxHlg - desiredMaxHlg) * 1000f / transitionDuration);
-
mHandler.removeCallbacks(mDebouncer);
mHandler.postDelayed(mDebouncer, debounceTime);
}
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 48d671d356f7..837fbf7ca17c 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -40,9 +40,9 @@ public class HdrBrightnessData {
public final long mBrightnessIncreaseDebounceMillis;
/**
- * Brightness increase animation duration
+ * Brightness increase animation speed
*/
- public final long mBrightnessIncreaseDurationMillis;
+ public final float mScreenBrightnessRampIncrease;
/**
* Debounce time for brightness decrease
@@ -50,19 +50,19 @@ public class HdrBrightnessData {
public final long mBrightnessDecreaseDebounceMillis;
/**
- * Brightness decrease animation duration
+ * Brightness decrease animation speed
*/
- public final long mBrightnessDecreaseDurationMillis;
+ public final float mScreenBrightnessRampDecrease;
@VisibleForTesting
public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
- long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
- long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+ long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
+ long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
mMaxBrightnessLimits = maxBrightnessLimits;
mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
- mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+ mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
- mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+ mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
}
@Override
@@ -70,9 +70,9 @@ public class HdrBrightnessData {
return "HdrBrightnessData {"
+ "mMaxBrightnessLimits: " + mMaxBrightnessLimits
+ ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
- + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+ + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
+ ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
- + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+ + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
+ "} ";
}
@@ -94,8 +94,8 @@ public class HdrBrightnessData {
return new HdrBrightnessData(brightnessLimits,
hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
- hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+ hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
- hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+ hdrConfig.getScreenBrightnessRampDecrease().floatValue());
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3435e560b1f9..0c4ecbce47d0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5529,6 +5529,42 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return canAccess;
}
+ @GuardedBy("ImfLock.class")
+ private void switchKeyboardLayoutLocked(int direction) {
+ final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
+ if (currentImi == null) {
+ return;
+ }
+ final InputMethodSubtypeHandle currentSubtypeHandle =
+ InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
+ final InputMethodSubtypeHandle nextSubtypeHandle =
+ mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
+ direction > 0);
+ if (nextSubtypeHandle == null) {
+ return;
+ }
+ final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
+ if (nextImi == null) {
+ return;
+ }
+
+ final int subtypeCount = nextImi.getSubtypeCount();
+ if (subtypeCount == 0) {
+ if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
+ setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
+ }
+ return;
+ }
+
+ for (int i = 0; i < subtypeCount; ++i) {
+ if (nextSubtypeHandle.equals(
+ InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
+ setInputMethodLocked(nextImi.getId(), i);
+ return;
+ }
+ }
+ }
+
private void publishLocalService() {
LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
}
@@ -5734,38 +5770,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void switchKeyboardLayout(int direction) {
synchronized (ImfLock.class) {
- final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
- if (currentImi == null) {
- return;
- }
- final InputMethodSubtypeHandle currentSubtypeHandle =
- InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
- final InputMethodSubtypeHandle nextSubtypeHandle =
- mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
- direction > 0);
- if (nextSubtypeHandle == null) {
- return;
- }
- final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
- if (nextImi == null) {
- return;
- }
-
- final int subtypeCount = nextImi.getSubtypeCount();
- if (subtypeCount == 0) {
- if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
- setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
- }
- return;
- }
-
- for (int i = 0; i < subtypeCount; ++i) {
- if (nextSubtypeHandle.equals(
- InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
- setInputMethodLocked(nextImi.getId(), i);
- return;
- }
- }
+ switchKeyboardLayoutLocked(direction);
}
}
@@ -6767,5 +6772,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
public void resetStylusHandwriting(int requestId) {
mImms.resetStylusHandwriting(requestId);
}
+
+ @BinderThread
+ @Override
+ public void switchKeyboardLayoutAsync(int direction) {
+ synchronized (ImfLock.class) {
+ if (!mImms.calledWithValidTokenLocked(mToken)) {
+ return;
+ }
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mImms.switchKeyboardLayoutLocked(direction);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 74b7f0866b54..323cdc54edae 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -474,13 +474,14 @@ public class LocationManagerService extends ILocationManager.Stub implements
// If we have a GNSS provider override, add the hardware provider as a standalone
// option for use by apps with the correct permission. Note the GNSS HAL can only
// support a single client, so mGnssManagerService.getGnssLocationProvider() can
- // only be installed with a single provider.
+ // only be installed with a single provider. Locations from this provider won't
+ // be reported through the passive provider.
LocationProviderManager gnssHardwareManager =
new LocationProviderManager(
mContext,
mInjector,
GPS_HARDWARE_PROVIDER,
- mPassiveManager,
+ /*passiveManager=*/ null,
Collections.singletonList(Manifest.permission.LOCATION_HARDWARE));
addLocationProviderManager(
gnssHardwareManager, mGnssManagerService.getGnssLocationProvider());
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index f168f435b5d7..f35b045471a6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -240,6 +240,10 @@ public class LockSettingsService extends ILockSettings.Stub {
private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts";
private static final String USER_SERIAL_NUMBER_KEY = "serial-number";
+ private static final String MIGRATED_FRP2 = "migrated_frp2";
+ private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
+ private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
+
// Duration that LockSettingsService will store the gatekeeper password for. This allows
// multiple biometric enrollments without prompting the user to enter their password via
// ConfirmLockPassword/ConfirmLockPattern multiple times. This needs to be at least the duration
@@ -909,14 +913,14 @@ public class LockSettingsService extends ILockSettings.Stub {
}
private void migrateOldData() {
- if (getString("migrated_keystore_namespace", null, 0) == null) {
+ if (getString(MIGRATED_KEYSTORE_NS, null, 0) == null) {
boolean success = true;
synchronized (mSpManager) {
success &= mSpManager.migrateKeyNamespace();
}
success &= migrateProfileLockKeys();
if (success) {
- setString("migrated_keystore_namespace", "true", 0);
+ setString(MIGRATED_KEYSTORE_NS, "true", 0);
Slog.i(TAG, "Migrated keys to LSS namespace");
} else {
Slog.w(TAG, "Failed to migrate keys to LSS namespace");
@@ -936,9 +940,9 @@ public class LockSettingsService extends ILockSettings.Stub {
// "migrated_frp" to "migrated_frp2" to cause migrateFrpCredential() to run again on devices
// where it had run before.
if (LockPatternUtils.frpCredentialEnabled(mContext)
- && !getBoolean("migrated_frp2", false, 0)) {
+ && !getBoolean(MIGRATED_FRP2, false, 0)) {
migrateFrpCredential();
- setBoolean("migrated_frp2", true, 0);
+ setBoolean(MIGRATED_FRP2, true, 0);
}
}
@@ -1028,14 +1032,14 @@ public class LockSettingsService extends ILockSettings.Stub {
// If this gets interrupted (e.g. by the device powering off), there shouldn't be a
// problem since this will run again on the next boot, and setUserKeyProtection() is
// okay with the key being already protected by the given secret.
- if (getString("migrated_all_users_to_sp_and_bound_ce", null, 0) == null) {
+ if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null) {
for (UserInfo user : mUserManager.getAliveUsers()) {
removeStateForReusedUserIdIfNecessary(user.id, user.serialNumber);
synchronized (mSpManager) {
migrateUserToSpWithBoundCeKeyLocked(user.id);
}
}
- setString("migrated_all_users_to_sp_and_bound_ce", "true", 0);
+ setString(MIGRATED_SP_CE_ONLY, "true", 0);
}
mThirdPartyAppsStarted = true;
@@ -1062,7 +1066,7 @@ public class LockSettingsService extends ILockSettings.Stub {
Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
return;
}
- setUserKeyProtection(userId, result.syntheticPassword.deriveFileBasedEncryptionKey());
+ setUserKeyProtection(userId, result.syntheticPassword);
}
}
@@ -1347,8 +1351,8 @@ public class LockSettingsService extends ILockSettings.Stub {
AndroidKeyStoreMaintenance.onUserPasswordChanged(userHandle, password);
}
- private void unlockKeystore(byte[] password, int userHandle) {
- Authorization.onLockScreenEvent(false, userHandle, password, null);
+ private void unlockKeystore(int userId, SyntheticPassword sp) {
+ Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null);
}
@VisibleForTesting /** Note: this method is overridden in unit tests */
@@ -2001,7 +2005,8 @@ public class LockSettingsService extends ILockSettings.Stub {
mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext));
}
- private void setUserKeyProtection(@UserIdInt int userId, byte[] secret) {
+ private void setUserKeyProtection(@UserIdInt int userId, SyntheticPassword sp) {
+ final byte[] secret = sp.deriveFileBasedEncryptionKey();
final long callingId = Binder.clearCallingIdentity();
try {
mStorageManager.setUserKeyProtection(userId, secret);
@@ -2045,7 +2050,9 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- private void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+ @Override
+ public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
+ checkPasswordReadPermission();
synchronized (mSpManager) {
if (isUserKeyUnlocked(userId)) {
Slogf.d(TAG, "CE storage for user %d is already unlocked", userId);
@@ -2768,7 +2775,7 @@ public class LockSettingsService extends ILockSettings.Stub {
final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
- setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
+ setUserKeyProtection(userId, sp);
onSyntheticPasswordCreated(userId, sp);
Slogf.i(TAG, "Successfully initialized synthetic password for user %d", userId);
return sp;
@@ -2827,7 +2834,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- unlockKeystore(sp.deriveKeyStorePassword(), userId);
+ unlockKeystore(userId, sp);
unlockUserKey(userId, sp);
@@ -2894,7 +2901,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mSpManager.clearSidForUser(userId);
gateKeeperClearSecureUserId(userId);
unlockUserKey(userId, sp);
- unlockKeystore(sp.deriveKeyStorePassword(), userId);
+ unlockKeystore(userId, sp);
setKeystorePassword(null, userId);
removeBiometricsForUser(userId);
}
@@ -3454,11 +3461,6 @@ public class LockSettingsService extends ILockSettings.Stub {
}
@Override
- public void unlockUserKeyIfUnsecured(@UserIdInt int userId) {
- LockSettingsService.this.unlockUserKeyIfUnsecured(userId);
- }
-
- @Override
public void createNewUser(@UserIdInt int userId, int userSerialNumber) {
LockSettingsService.this.createNewUser(userId, userSerialNumber);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index df95c69e7271..4bac872dbaa9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -174,7 +174,7 @@ class LockSettingsShellCommand extends ShellCommand {
pw.println(" Sets the lock screen as PIN, using the given PIN to unlock.");
pw.println("");
pw.println(" set-password [--old <CREDENTIAL>] [--user USER_ID] <PASSWORD>");
- pw.println(" Sets the lock screen as password, using the given PASSOWRD to unlock.");
+ pw.println(" Sets the lock screen as password, using the given PASSWORD to unlock.");
pw.println("");
pw.println(" clear [--old <CREDENTIAL>] [--user USER_ID]");
pw.println(" Clears the lock credentials.");
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
index 5d5d59bcb6ef..8cb334dc2260 100644
--- a/services/core/java/com/android/server/media/AudioAttributesUtils.java
+++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java
@@ -23,6 +23,8 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.MediaRoute2Info;
+import com.android.media.flags.Flags;
+
/* package */ final class AudioAttributesUtils {
/* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
@@ -36,6 +38,14 @@ import android.media.MediaRoute2Info;
@MediaRoute2Info.Type
/* package */ static int mapToMediaRouteType(
@NonNull AudioDeviceAttributes audioDeviceAttributes) {
+ if (Flags.enableAudioPoliciesDeviceAndBluetoothController()) {
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_HDMI_ARC:
+ return MediaRoute2Info.TYPE_HDMI_ARC;
+ case AudioDeviceInfo.TYPE_HDMI_EARC:
+ return MediaRoute2Info.TYPE_HDMI_EARC;
+ }
+ }
switch (audioDeviceAttributes.getType()) {
case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
@@ -64,7 +74,6 @@ import android.media.MediaRoute2Info;
}
}
-
/* package */ static boolean isDeviceOutputAttributes(
@Nullable AudioDeviceAttributes audioDeviceAttributes) {
if (audioDeviceAttributes == null) {
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 33190ade4f42..360a6a721988 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -22,6 +22,8 @@ import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
import static android.media.MediaRoute2Info.TYPE_DOCK;
import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_HDMI_ARC;
+import static android.media.MediaRoute2Info.TYPE_HDMI_EARC;
import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
@@ -160,7 +162,6 @@ import java.util.Objects;
@NonNull
private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) {
int name = R.string.default_audio_route_name;
-
switch (type) {
case TYPE_WIRED_HEADPHONES:
case TYPE_WIRED_HEADSET:
@@ -170,6 +171,8 @@ import java.util.Objects;
name = R.string.default_audio_route_name_dock_speakers;
break;
case TYPE_HDMI:
+ case TYPE_HDMI_ARC:
+ case TYPE_HDMI_EARC:
name = R.string.default_audio_route_name_external_device;
break;
case TYPE_USB_DEVICE:
@@ -211,6 +214,8 @@ import java.util.Objects;
case TYPE_WIRED_HEADSET:
case TYPE_DOCK:
case TYPE_HDMI:
+ case TYPE_HDMI_ARC:
+ case TYPE_HDMI_EARC:
case TYPE_USB_DEVICE:
return true;
default:
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 0e8f90795ef9..2c595116afed 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -268,7 +268,14 @@ public class MediaSessionService extends SystemService implements Monitor {
}
if (record.isSystemPriority()) {
if (DEBUG_KEY_EVENT) {
- Log.d(TAG, "Global priority session is updated, active=" + record.isActive());
+ Log.d(
+ TAG,
+ "Global priority session updated - user id="
+ + record.getUserId()
+ + " package="
+ + record.getPackageName()
+ + " active="
+ + record.isActive());
}
user.pushAddressedPlayerChangedLocked();
} else {
diff --git a/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
new file mode 100644
index 000000000000..5bad06777328
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/FrameworkStatsLogWrapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.media.projection;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Wrapper around {@link FrameworkStatsLog} */
+public class FrameworkStatsLogWrapper {
+
+ /** Wrapper around {@link FrameworkStatsLog#write}. */
+ public void write(
+ int code,
+ int sessionId,
+ int state,
+ int previousState,
+ int hostUid,
+ int targetUid,
+ int timeSinceLastActive,
+ int creationSource) {
+ FrameworkStatsLog.write(
+ code,
+ sessionId,
+ state,
+ previousState,
+ hostUid,
+ targetUid,
+ timeSinceLastActive,
+ creationSource);
+ }
+}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 8cbc368467bb..58927d14ee23 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -76,7 +76,6 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
@@ -162,7 +161,7 @@ public final class MediaProjectionManagerService extends SystemService
mWmInternal = LocalServices.getService(WindowManagerInternal.class);
mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mMediaRouterCallback = new MediaRouterCallback();
- mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger();
+ mMediaProjectionMetricsLogger = injector.mediaProjectionMetricsLogger(context);
Watchdog.getInstance().addMonitor(this);
}
@@ -197,8 +196,8 @@ public final class MediaProjectionManagerService extends SystemService
return Looper.getMainLooper();
}
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
- return MediaProjectionMetricsLogger.getInstance();
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
+ return MediaProjectionMetricsLogger.getInstance(context);
}
}
@@ -293,6 +292,12 @@ public final class MediaProjectionManagerService extends SystemService
private void stopProjectionLocked(final MediaProjection projection) {
Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
+ "dispatching stop to callbacks");
+ ContentRecordingSession session = projection.mSession;
+ int targetUid =
+ session != null
+ ? session.getTargetUid()
+ : ContentRecordingSession.TARGET_UID_UNKNOWN;
+ mMediaProjectionMetricsLogger.logStopped(projection.uid, targetUid);
mProjectionToken = null;
mProjectionGrant = null;
dispatchStop(projection);
@@ -379,10 +384,12 @@ public final class MediaProjectionManagerService extends SystemService
if (mProjectionGrant != null) {
// Cache the session details.
mProjectionGrant.mSession = incomingSession;
- mMediaProjectionMetricsLogger.notifyProjectionStateChange(
- mProjectionGrant.uid,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ if (incomingSession != null) {
+ // Only log in progress when session is not null.
+ // setContentRecordingSession is called with a null session for the stop case.
+ mMediaProjectionMetricsLogger.logInProgress(
+ mProjectionGrant.uid, incomingSession.getTargetUid());
+ }
dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession);
}
return true;
@@ -452,6 +459,21 @@ public final class MediaProjectionManagerService extends SystemService
.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
}
+ @VisibleForTesting
+ void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+ mMediaProjectionMetricsLogger.logInitiated(hostUid, sessionCreationSource);
+ }
+
+ @VisibleForTesting
+ void notifyPermissionRequestDisplayed(int hostUid) {
+ mMediaProjectionMetricsLogger.logPermissionRequestDisplayed(hostUid);
+ }
+
+ @VisibleForTesting
+ void notifyAppSelectorDisplayed(int hostUid) {
+ mMediaProjectionMetricsLogger.logAppSelectorDisplayed(hostUid);
+ }
+
/**
* Handles result of dialog shown from
* {@link BinderService#buildReviewGrantedConsentIntentLocked()}.
@@ -842,6 +864,43 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyPermissionRequestInitiated(int hostUid, int sessionCreationSource) {
+ notifyPermissionRequestInitiated_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.notifyPermissionRequestInitiated(
+ hostUid, sessionCreationSource);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyPermissionRequestDisplayed(int hostUid) {
+ notifyPermissionRequestDisplayed_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.notifyPermissionRequestDisplayed(hostUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ @EnforcePermission(MANAGE_MEDIA_PROJECTION)
+ public void notifyAppSelectorDisplayed(int hostUid) {
+ notifyAppSelectorDisplayed_enforcePermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ MediaProjectionManagerService.this.notifyAppSelectorDisplayed(hostUid);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
index f18ecad09c42..55a30bf225a3 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionMetricsLogger.java
@@ -16,35 +16,197 @@
package com.android.server.media.projection;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+
+import android.content.Context;
+import android.util.Log;
import com.android.internal.util.FrameworkStatsLog;
-/**
- * Class for emitting logs describing a MediaProjection session.
- */
+import java.time.Duration;
+
+/** Class for emitting logs describing a MediaProjection session. */
public class MediaProjectionMetricsLogger {
+ private static final String TAG = "MediaProjectionMetricsLogger";
+
+ private static final int TARGET_UID_UNKNOWN = -2;
+ private static final int TIME_SINCE_LAST_ACTIVE_UNKNOWN = -1;
+
private static MediaProjectionMetricsLogger sSingleton = null;
- public static MediaProjectionMetricsLogger getInstance() {
+ private final FrameworkStatsLogWrapper mFrameworkStatsLogWrapper;
+ private final MediaProjectionSessionIdGenerator mSessionIdGenerator;
+ private final MediaProjectionTimestampStore mTimestampStore;
+
+ private int mPreviousState =
+ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+
+ MediaProjectionMetricsLogger(
+ FrameworkStatsLogWrapper frameworkStatsLogWrapper,
+ MediaProjectionSessionIdGenerator sessionIdGenerator,
+ MediaProjectionTimestampStore timestampStore) {
+ mFrameworkStatsLogWrapper = frameworkStatsLogWrapper;
+ mSessionIdGenerator = sessionIdGenerator;
+ mTimestampStore = timestampStore;
+ }
+
+ /** Returns a singleton instance of {@link MediaProjectionMetricsLogger}. */
+ public static MediaProjectionMetricsLogger getInstance(Context context) {
if (sSingleton == null) {
- sSingleton = new MediaProjectionMetricsLogger();
+ sSingleton =
+ new MediaProjectionMetricsLogger(
+ new FrameworkStatsLogWrapper(),
+ MediaProjectionSessionIdGenerator.getInstance(context),
+ MediaProjectionTimestampStore.getInstance(context));
}
return sSingleton;
}
- void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
+ /**
+ * Logs that the media projection session was initiated by the app requesting the user's consent
+ * to capture. Should be sent even if the permission dialog is not shown.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ * @param sessionCreationSource Where this session started. One of:
+ * <ul>
+ * <li>{@link
+ * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP}
+ * <li>{@link
+ * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST}
+ * <li>{@link
+ * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER}
+ * <li>{@link
+ * FrameworkStatsLog#MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN}
+ * </ul>
+ */
+ public void logInitiated(int hostUid, int sessionCreationSource) {
+ Log.d(TAG, "logInitiated");
+ Duration durationSinceLastActiveSession = mTimestampStore.timeSinceLastActiveSession();
+ int timeSinceLastActiveInSeconds =
+ durationSinceLastActiveSession == null
+ ? TIME_SINCE_LAST_ACTIVE_UNKNOWN
+ : (int) durationSinceLastActiveSession.toSeconds();
+ write(
+ mSessionIdGenerator.createAndGetNewSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+ hostUid,
+ TARGET_UID_UNKNOWN,
+ timeSinceLastActiveInSeconds,
+ sessionCreationSource);
+ }
+
+ /**
+ * Logs that the user entered the setup flow and permission dialog is displayed. This state is
+ * not sent when the permission is already granted and we skipped showing the permission dialog.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ */
+ public void logPermissionRequestDisplayed(int hostUid) {
+ Log.d(TAG, "logPermissionRequestDisplayed");
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED,
+ hostUid,
+ TARGET_UID_UNKNOWN,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
+ * Logs that the app selector dialog is shown for the user.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ */
+ public void logAppSelectorDisplayed(int hostUid) {
+ Log.d(TAG, "logAppSelectorDisplayed");
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED,
+ hostUid,
+ TARGET_UID_UNKNOWN,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
+ * Logs that the virtual display is created and capturing the selected region begins.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ * @param targetUid UID of the package that is captured if selected.
+ */
+ public void logInProgress(int hostUid, int targetUid) {
+ Log.d(TAG, "logInProgress");
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
+ hostUid,
+ targetUid,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ /**
+ * Logs that the capturing stopped, either normally or because of error.
+ *
+ * @param hostUid UID of the package that initiates MediaProjection.
+ * @param targetUid UID of the package that is captured if selected.
+ */
+ public void logStopped(int hostUid, int targetUid) {
+ boolean wasCaptureInProgress =
+ mPreviousState
+ == MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+ Log.d(TAG, "logStopped: wasCaptureInProgress=" + wasCaptureInProgress);
+ write(
+ mSessionIdGenerator.getCurrentSessionId(),
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED,
+ hostUid,
+ targetUid,
+ TIME_SINCE_LAST_ACTIVE_UNKNOWN,
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+
+ if (wasCaptureInProgress) {
+ mTimestampStore.registerActiveSessionEnded();
+ }
+ }
+
+ public void notifyProjectionStateChange(int hostUid, int state, int sessionCreationSource) {
write(hostUid, state, sessionCreationSource);
}
private void write(int hostUid, int state, int sessionCreationSource) {
- FrameworkStatsLog.write(
+ mFrameworkStatsLogWrapper.write(
/* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
/* session_id */ 123,
/* state */ state,
- /* previous_state */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
+ /* previous_state */ FrameworkStatsLog
+ .MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN,
/* host_uid */ hostUid,
/* target_uid */ -1,
/* time_since_last_active */ 0,
/* creation_source */ sessionCreationSource);
}
+
+ private void write(
+ int sessionId,
+ int state,
+ int hostUid,
+ int targetUid,
+ int timeSinceLastActive,
+ int creationSource) {
+ mFrameworkStatsLogWrapper.write(
+ /* code */ FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED,
+ sessionId,
+ state,
+ mPreviousState,
+ hostUid,
+ targetUid,
+ timeSinceLastActive,
+ creationSource);
+ mPreviousState = state;
+ }
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
index ff70cb35f9dc..244de0b3dd99 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
@@ -47,8 +47,11 @@ public class MediaProjectionSessionIdGenerator {
if (sInstance == null) {
File preferencesFile =
new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+ // Needed as this class is instantiated before the device is unlocked.
+ Context directBootContext = context.createDeviceProtectedStorageContext();
SharedPreferences preferences =
- context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+ directBootContext.getSharedPreferences(
+ preferencesFile, Context.MODE_PRIVATE);
sInstance = new MediaProjectionSessionIdGenerator(preferences);
}
return sInstance;
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
index 4026d0c43484..bfec58c8983d 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionTimestampStore.java
@@ -59,8 +59,11 @@ public class MediaProjectionTimestampStore {
if (sInstance == null) {
File preferencesFile =
new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+ // Needed as this class is instantiated before the device is unlocked.
+ Context directBootContext = context.createDeviceProtectedStorageContext();
SharedPreferences preferences =
- context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+ directBootContext.getSharedPreferences(
+ preferencesFile, Context.MODE_PRIVATE);
sInstance = new MediaProjectionTimestampStore(preferences, InstantSource.system());
}
return sInstance;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b4d36db96c01..7ca56990f2d0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -71,6 +71,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static android.os.Flags.allowPrivateProfile;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
@@ -289,7 +290,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.MetricsLogger;
@@ -1179,7 +1179,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void onSetDisabled(int status) {
synchronized (mNotificationLock) {
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateDisableNotificationEffectsLocked(status);
} else {
mDisableNotificationEffects =
@@ -1325,7 +1325,7 @@ public class NotificationManagerService extends SystemService {
public void clearEffects() {
synchronized (mNotificationLock) {
if (DBG) Slog.d(TAG, "clearEffects");
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.clearAttentionEffects();
} else {
clearSoundLocked();
@@ -1554,8 +1554,7 @@ public class NotificationManagerService extends SystemService {
int changedFlags = data.getFlags() ^ flags;
if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
// Suppress notification flag changed, clear any effects
- if (mFlagResolver.isEnabled(
- NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.clearEffectsLocked(key);
} else {
clearEffectsLocked(key);
@@ -1904,7 +1903,7 @@ public class NotificationManagerService extends SystemService {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
if (action.equals(Intent.ACTION_SCREEN_ON)) {
// Keep track of screen on/off state, but do not turn off the notification light
// until user passes through the lock screen or views the notification.
@@ -1931,7 +1930,8 @@ public class NotificationManagerService extends SystemService {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
REASON_USER_STOPPED);
}
- } else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
+ } else if (
+ isProfileUnavailable(action)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, userHandle,
@@ -1982,6 +1982,12 @@ public class NotificationManagerService extends SystemService {
}
}
}
+
+ private boolean isProfileUnavailable(String action) {
+ return allowPrivateProfile() ?
+ action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
+ action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ }
};
private final class SettingsObserver extends ContentObserver {
@@ -2011,7 +2017,7 @@ public class NotificationManagerService extends SystemService {
ContentResolver resolver = getContext().getContentResolver();
resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
false, this, UserHandle.USER_ALL);
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
false, this, UserHandle.USER_ALL);
}
@@ -2037,7 +2043,7 @@ public class NotificationManagerService extends SystemService {
public void update(Uri uri) {
ContentResolver resolver = getContext().getContentResolver();
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
boolean pulseEnabled = Settings.System.getIntForUser(resolver,
Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
@@ -2530,7 +2536,7 @@ public class NotificationManagerService extends SystemService {
mToastRateLimiter = toastRateLimiter;
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
mNotificationManagerPrivate, mZenModeHelper, flagResolver);
@@ -2540,7 +2546,7 @@ public class NotificationManagerService extends SystemService {
// If this is called within a test, make sure to unregister the intent receivers by
// calling onDestroy()
IntentFilter filter = new IntentFilter();
- if (!mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (!Flags.refactorAttentionHelper()) {
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -2552,6 +2558,9 @@ public class NotificationManagerService extends SystemService {
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ if (allowPrivateProfile()){
+ filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
+ }
getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
IntentFilter pkgFilter = new IntentFilter();
@@ -2865,7 +2874,7 @@ public class NotificationManagerService extends SystemService {
}
registerNotificationPreferencesPullers();
new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.onSystemReady();
}
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -6490,7 +6499,7 @@ public class NotificationManagerService extends SystemService {
pw.println(" mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
pw.println(" hideSilentStatusBar="
+ mPreferencesHelper.shouldHideSilentStatusIcons());
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.dump(pw, " ", filter);
}
}
@@ -7756,7 +7765,7 @@ public class NotificationManagerService extends SystemService {
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
SystemClock.elapsedRealtime());
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
@@ -7889,7 +7898,7 @@ public class NotificationManagerService extends SystemService {
cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
mSendDelete, childrenFlagChecker, mReason,
mCancellationElapsedTimeMs);
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
@@ -8186,7 +8195,7 @@ public class NotificationManagerService extends SystemService {
int buzzBeepBlinkLoggingCode = 0;
if (!r.isHidden()) {
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
new NotificationAttentionHelper.Signals(
mUserProfiles.isCurrentProfile(r.getUserId()),
@@ -9173,7 +9182,7 @@ public class NotificationManagerService extends SystemService {
|| interruptiveChanged;
if (interceptBefore && !record.isIntercepted()
&& record.isNewEnoughForAlerting(System.currentTimeMillis())) {
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.buzzBeepBlinkLocked(record,
new NotificationAttentionHelper.Signals(
mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
@@ -9553,7 +9562,7 @@ public class NotificationManagerService extends SystemService {
});
}
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.clearEffectsLocked(canceledKey);
} else {
// sound
@@ -9917,7 +9926,7 @@ public class NotificationManagerService extends SystemService {
cancellationElapsedTimeMs);
}
}
- if (mFlagResolver.isEnabled(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mAttentionHelper.updateLightsLocked();
} else {
updateLightsLocked();
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 30017be96085..510c06e9509e 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -24,7 +24,6 @@ import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfi
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.CATEGORY_HOME;
-import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -1525,9 +1524,6 @@ public class ComputerEngine implements Computer {
ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
ai.volumeUuid = ps.getVolumeUuid();
ai.storageUuid = StorageManager.convert(ai.volumeUuid);
- if (ps.isDefaultToDeviceProtectedStorage()) {
- ai.privateFlags |= PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
- }
ai.setVersionCode(ps.getVersionCode());
ai.flags = ps.getFlags();
ai.privateFlags = ps.getPrivateFlags();
@@ -4596,6 +4592,7 @@ public class ComputerEngine implements Computer {
flags = updateFlagsForApplication(flags, userId);
final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
final boolean listApex = (flags & MATCH_APEX) != 0;
+ final boolean listArchivedOnly = !listUninstalled && (flags & MATCH_ARCHIVED_PACKAGES) != 0;
enforceCrossUserPermission(
callingUid,
@@ -4607,7 +4604,7 @@ public class ComputerEngine implements Computer {
ArrayList<ApplicationInfo> list;
final ArrayMap<String, ? extends PackageStateInternal> packageStates =
getPackageStates();
- if (listUninstalled) {
+ if (listUninstalled || listArchivedOnly) {
list = new ArrayList<>(packageStates.size());
for (PackageStateInternal ps : packageStates.values()) {
ApplicationInfo ai;
@@ -4619,6 +4616,11 @@ public class ComputerEngine implements Computer {
if (!listApex && ps.getPkg().isApex()) {
continue;
}
+ PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+ if (listArchivedOnly && !userState.isInstalled()
+ && userState.getArchiveState() == null) {
+ continue;
+ }
if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 29678181679b..7cac870088d8 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -16,7 +16,7 @@
package com.android.server.pm;
-import static android.content.pm.Flags.preventSdkLibApp;
+import static android.content.pm.Flags.disallowSdkLibsToBeApps;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
@@ -996,7 +996,7 @@ final class InstallPackageHelper {
}
final boolean isApex = (request.getScanFlags() & SCAN_AS_APEX) != 0;
final boolean isSdkLibrary = packageToScan.isSdkLibrary();
- if (isApex || (isSdkLibrary && preventSdkLibApp())) {
+ if (isApex || (isSdkLibrary && disallowSdkLibsToBeApps())) {
request.getScannedPackageSetting().setAppId(Process.INVALID_UID);
} else {
createdAppId.put(packageName, optimisticallyRegisterAppId(request));
@@ -1630,7 +1630,8 @@ final class InstallPackageHelper {
synchronized (mPm.mLock) {
if (DEBUG_INSTALL) {
Slog.d(TAG,
- "replacePackageLI: new=" + parsedPackage + ", old=" + oldPackage);
+ "replacePackageLI: new=" + parsedPackage
+ + ", old=" + oldPackageState.getName());
}
ps = mPm.mSettings.getPackageLPr(pkgName11);
@@ -1789,7 +1790,7 @@ final class InstallPackageHelper {
if (DEBUG_INSTALL) {
Slog.d(TAG, "replaceSystemPackageLI: new=" + parsedPackage
- + ", old=" + oldPackage);
+ + ", old=" + oldPackageState.getName());
}
request.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
request.setApexModuleName(oldPackageState.getApexModuleName());
@@ -1799,7 +1800,7 @@ final class InstallPackageHelper {
if (DEBUG_INSTALL) {
Slog.d(TAG,
"replaceNonSystemPackageLI: new=" + parsedPackage + ", old="
- + oldPackage);
+ + oldPackageState.getName());
}
}
} else { // new package install
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index a161e8c39ca2..1135466ae1d4 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -65,6 +65,7 @@ import android.content.pm.IncrementalStatesInfo;
import android.content.pm.LauncherActivityInfoInternal;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -1377,6 +1378,25 @@ public class LauncherAppsService extends SystemService {
}
@Override
+ public @Nullable LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle user) {
+ // Only system launchers, which have access to recents should have access to this API.
+ // TODO(b/303803157): Add the new permission check if we decide to have one.
+ if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+ throw new SecurityException("Caller is not the recents app");
+ }
+ if (!canAccessProfile(user.getIdentifier(),
+ "Can't access LauncherUserInfo for another user")) {
+ return null;
+ }
+ long ident = injectClearCallingIdentity();
+ try {
+ return mUserManagerInternal.getLauncherUserInfo(user.getIdentifier());
+ } finally {
+ injectRestoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public void startActivityAsUser(IApplicationThread caller, String callingPackage,
String callingFeatureId, ComponentName component, Rect sourceBounds,
Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 0fb1f7a0780c..781e5f891a43 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -38,6 +38,8 @@ import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -48,6 +50,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelableException;
+import android.os.Process;
import android.os.SELinux;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -162,15 +165,13 @@ public class PackageArchiver {
});
}
- /**
- * Creates archived state for the package and user.
- */
- public CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
+ /** Creates archived state for the package and user. */
+ private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
throws PackageManager.NameNotFoundException {
PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
Binder.getCallingUid(), userId);
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
- verifyInstaller(responsibleInstallerPackage);
+ verifyInstaller(responsibleInstallerPackage, userId);
List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
userId);
@@ -268,27 +269,34 @@ public class PackageArchiver {
return iconFile.toPath();
}
- private void verifyInstaller(String installerPackage)
+ private void verifyInstaller(String installerPackage, int userId)
throws PackageManager.NameNotFoundException {
if (TextUtils.isEmpty(installerPackage)) {
throw new PackageManager.NameNotFoundException("No installer found");
}
- if (!verifySupportsUnarchival(installerPackage)) {
+ // Allow shell for easier development.
+ if ((Binder.getCallingUid() != Process.SHELL_UID)
+ && !verifySupportsUnarchival(installerPackage, userId)) {
throw new PackageManager.NameNotFoundException("Installer does not support unarchival");
}
}
/**
- * @return true if installerPackage support unarchival:
- * - has an action Intent.ACTION_UNARCHIVE_PACKAGE,
- * - has permissions to install packages.
+ * Returns true if {@code installerPackage} supports unarchival being able to handle
+ * {@link Intent#ACTION_UNARCHIVE_PACKAGE}
*/
- public boolean verifySupportsUnarchival(String installerPackage) {
- // TODO(b/278553670) Check if installerPackage supports unarchival.
+ public boolean verifySupportsUnarchival(String installerPackage, int userId) {
if (TextUtils.isEmpty(installerPackage)) {
return false;
}
- return true;
+
+ Intent intent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE).setPackage(installerPackage);
+
+ ParceledListSlice<ResolveInfo> intentReceivers =
+ Binder.withCleanCallingIdentity(
+ () -> mPm.queryIntentReceivers(mPm.snapshotComputer(),
+ intent, /* resolvedType= */ null, /* flags= */ 0, userId));
+ return intentReceivers != null && !intentReceivers.getList().isEmpty();
}
void requestUnarchive(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d0e5f96f8d0f..5dc7dab73141 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3445,7 +3445,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
- getInstallSource().mInstallerPackageName)) {
+ getInstallSource().mInstallerPackageName, userId)) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_SESSION_INVALID,
"Installer has to support unarchival in order to install archived "
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 839b6998bd8b..61b6b24ba97e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4566,6 +4566,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, pmi.getPackageUid(packageName, 0, userId));
extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+ extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED,
packageName, extras,
@@ -6969,6 +6970,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final Bundle extras = new Bundle();
extras.putInt(Intent.EXTRA_UID, uid);
extras.putInt(Intent.EXTRA_USER_HANDLE, userId);
+ extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime());
mHandler.post(() -> {
mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED,
packageName, extras,
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3ca933a66656..ad26d1fa8587 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -89,17 +90,14 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
private static class Booleans {
@IntDef({
INSTALL_PERMISSION_FIXED,
- DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
UPDATE_AVAILABLE,
FORCE_QUERYABLE_OVERRIDE
})
public @interface Flags {
}
private static final int INSTALL_PERMISSION_FIXED = 1;
- private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
- private static final int UPDATE_AVAILABLE = 1 << 2;
- private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
- private static final int PERSISTENT = 1 << 4;
+ private static final int UPDATE_AVAILABLE = 1 << 1;
+ private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
}
private int mBooleans;
@@ -238,34 +236,16 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public PackageSetting(String name, String realName, @NonNull File path,
- String legacyNativeLibraryPath, String primaryCpuAbi,
- String secondaryCpuAbi, String cpuAbiOverride,
- long longVersionCode, int pkgFlags, int pkgPrivateFlags,
- int sharedUserAppId,
- String[] usesSdkLibraries, long[] usesSdkLibrariesVersionsMajor,
- String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Map<String, Set<String>> mimeGroups,
- @NonNull UUID domainSetId) {
+ public PackageSetting(@NonNull String name, @Nullable String realName, @NonNull File path,
+ int pkgFlags, int pkgPrivateFlags, @NonNull UUID domainSetId) {
super(pkgFlags, pkgPrivateFlags);
this.mName = name;
this.mRealName = realName;
- this.usesSdkLibraries = usesSdkLibraries;
- this.usesSdkLibrariesVersionsMajor = usesSdkLibrariesVersionsMajor;
- this.usesStaticLibraries = usesStaticLibraries;
- this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
this.mPath = path;
this.mPathString = path.toString();
- this.legacyNativeLibraryPath = legacyNativeLibraryPath;
- this.mPrimaryCpuAbi = primaryCpuAbi;
- this.mSecondaryCpuAbi = secondaryCpuAbi;
- this.mCpuAbiOverride = cpuAbiOverride;
- this.versionCode = longVersionCode;
this.signatures = new PackageSignatures();
this.installSource = InstallSource.EMPTY;
- this.mSharedUserAppId = sharedUserAppId;
- mDomainSetId = domainSetId;
- copyMimeGroups(mimeGroups);
+ this.mDomainSetId = domainSetId;
mSnapshot = makeCache();
}
@@ -518,13 +498,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return this;
}
- public PackageSetting setDefaultToDeviceProtectedStorage(
- boolean defaultToDeviceProtectedStorage) {
- setBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE, defaultToDeviceProtectedStorage);
- onChanged();
- return this;
- }
-
@Override
public boolean isExternalStorage() {
return (getFlags() & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
@@ -536,14 +509,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return this;
}
- public void setSharedUserAppId(int sharedUserAppId) {
+ public PackageSetting setSharedUserAppId(int sharedUserAppId) {
mSharedUserAppId = sharedUserAppId;
onChanged();
- }
-
- public PackageSetting setIsPersistent(boolean isPersistent) {
- setBoolean(Booleans.PERSISTENT, isPersistent);
- onChanged();
return this;
}
@@ -576,7 +544,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
+ " " + mName + "/" + mAppId + "}";
}
- protected void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
+ private void copyMimeGroups(@Nullable Map<String, Set<String>> newMimeGroups) {
if (newMimeGroups == null) {
mimeGroups = null;
return;
@@ -1250,7 +1218,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
/**
* @see #mPath
*/
- PackageSetting setPath(@NonNull File path) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public PackageSetting setPath(@NonNull File path) {
this.mPath = path;
this.mPathString = path.toString();
onChanged();
@@ -1451,9 +1420,11 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return this;
}
- public PackageSetting setMimeGroups(@NonNull Map<String, Set<String>> mimeGroups) {
- this.mimeGroups = mimeGroups;
- onChanged();
+ public PackageSetting setMimeGroups(@Nullable Map<String, Set<String>> mimeGroups) {
+ if (mimeGroups != null) {
+ copyMimeGroups(mimeGroups);
+ onChanged();
+ }
return this;
}
@@ -1599,12 +1570,12 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
*/
@Override
public boolean isDefaultToDeviceProtectedStorage() {
- return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
+ return (getPrivateFlags() & PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) != 0;
}
@Override
public boolean isPersistent() {
- return getBoolean(Booleans.PERSISTENT);
+ return (getFlags() & ApplicationInfo.FLAG_PERSISTENT) != 0;
}
@@ -1760,10 +1731,10 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
}
@DataClass.Generated(
- time = 1696979728639L,
+ time = 1698097434269L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setIsPersistent(boolean)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int PERSISTENT\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mBooleans\nprivate int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate int mTargetSdkVersion\nprivate @android.annotation.Nullable byte[] mRestrictUpdateHash\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic com.android.server.pm.PackageSetting setSharedUserAppId(int)\npublic com.android.server.pm.PackageSetting setTargetSdkVersion(int)\npublic com.android.server.pm.PackageSetting setRestrictUpdateHash(byte[])\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprivate void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic boolean isRequestLegacyExternalStorage()\npublic boolean isUserDataFragile()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isInstalledOrHasDataOnAnyOtherUser(int[],int)\n int[] queryInstalledUsers(int[],boolean)\n int[] queryUsersInstalledOrHasData(int[])\n long getCeDataInode(int)\n long getDeDataInode(int)\n void setCeDataInode(long,int)\n void setDeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\npublic @com.android.internal.annotations.VisibleForTesting com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isIncremental()\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\npublic @java.lang.Override boolean isPersistent()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final int INSTALL_PERMISSION_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 8d8acfd421de..7ea9e3f529d0 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -220,7 +220,7 @@ final class ScanPackageUtils {
UserManagerService.getInstance(), usesSdkLibraries,
parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries,
parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(),
- newDomainSetId, parsedPackage.isPersistent(),
+ newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
} else {
// make a deep copy to avoid modifying any existing system state.
@@ -241,7 +241,7 @@ final class ScanPackageUtils {
UserManagerService.getInstance(),
usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(),
usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(),
- parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.isPersistent(),
+ parsedPackage.getMimeGroups(), newDomainSetId,
parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash());
}
@@ -464,8 +464,6 @@ final class ScanPackageUtils {
+ " to " + volumeUuid);
pkgSetting.setVolumeUuid(volumeUuid);
}
- pkgSetting.setDefaultToDeviceProtectedStorage(
- parsedPackage.isDefaultToDeviceProtectedStorage());
SharedLibraryInfo sdkLibraryInfo = null;
if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e726d91c30a0..440823c43607 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -931,16 +931,23 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
sharedUserSetting.mDisabledPackages.remove(p);
}
p.getPkgState().setUpdatedSystemApp(false);
- PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(),
- p.getLegacyNativeLibraryPath(), p.getPrimaryCpuAbiLegacy(),
- p.getSecondaryCpuAbiLegacy(), p.getCpuAbiOverride(),
- p.getAppId(), p.getVersionCode(), p.getFlags(), p.getPrivateFlags(),
- p.getUsesSdkLibraries(), p.getUsesSdkLibrariesVersionsMajor(),
- p.getUsesStaticLibraries(), p.getUsesStaticLibrariesVersions(), p.getMimeGroups(),
- mDomainVerificationManager.generateNewId());
+ PackageSetting ret = addPackageLPw(name, p.getRealName(), p.getPath(), p.getAppId(),
+ p.getFlags(), p.getPrivateFlags(), mDomainVerificationManager.generateNewId());
if (ret != null) {
+ ret.setLegacyNativeLibraryPath(p.getLegacyNativeLibraryPath());
+ ret.setPrimaryCpuAbi(p.getPrimaryCpuAbiLegacy());
+ ret.setSecondaryCpuAbi(p.getSecondaryCpuAbiLegacy());
+ ret.setCpuAbiOverride(p.getCpuAbiOverride());
+ ret.setLongVersionCode(p.getVersionCode());
+ ret.setUsesSdkLibraries(p.getUsesSdkLibraries());
+ ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor());
+ ret.setUsesStaticLibraries(p.getUsesStaticLibraries());
+ ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions());
+ ret.setMimeGroups(p.getMimeGroups());
ret.setAppMetadataFilePath(p.getAppMetadataFilePath());
ret.getPkgState().setUpdatedSystemApp(false);
+ ret.setTargetSdkVersion(p.getTargetSdkVersion());
+ ret.setRestrictUpdateHash(p.getRestrictUpdateHash());
}
mDisabledSysPackages.remove(name);
return ret;
@@ -961,13 +968,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
}
}
- PackageSetting addPackageLPw(String name, String realName, File codePath,
- String legacyNativeLibraryPathString, String primaryCpuAbiString,
- String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, long vc,
- int pkgFlags, int pkgPrivateFlags, String[] usesSdkLibraries,
- long[] usesSdkLibrariesVersions, String[] usesStaticLibraries,
- long[] usesStaticLibrariesVersions, Map<String, Set<String>> mimeGroups,
- @NonNull UUID domainSetId) {
+ PackageSetting addPackageLPw(String name, String realName, File codePath, int uid, int pkgFlags,
+ int pkgPrivateFlags, @NonNull UUID domainSetId) {
PackageSetting p = mPackages.get(name);
if (p != null) {
if (p.getAppId() == uid) {
@@ -977,11 +979,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
"Adding duplicate package, keeping first: " + name);
return null;
}
- p = new PackageSetting(name, realName, codePath, legacyNativeLibraryPathString,
- primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString, vc, pkgFlags,
- pkgPrivateFlags, 0 /*userId*/, usesSdkLibraries, usesSdkLibrariesVersions,
- usesStaticLibraries, usesStaticLibrariesVersions, mimeGroups, domainSetId);
- p.setAppId(uid);
+ p = new PackageSetting(name, realName, codePath, pkgFlags, pkgPrivateFlags, domainSetId)
+ .setAppId(uid);
if (mAppIds.registerExistingAppId(uid, p, name)) {
mPackages.put(name, p);
return p;
@@ -1061,7 +1060,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager,
String[] usesSdkLibraries, long[] usesSdkLibrariesVersions,
String[] usesStaticLibraries, long[] usesStaticLibrariesVersions,
- Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ Set<String> mimeGroupNames, @NonNull UUID domainSetId,
int targetSdkVersion, byte[] restrictUpdatedHash) {
final PackageSetting pkgSetting;
if (originalPkg != null) {
@@ -1083,7 +1082,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
.setDomainSetId(domainSetId)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash);
pkgSetting.setFlags(pkgFlags)
@@ -1092,16 +1090,20 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
int installUserId = installUser != null ? installUser.getIdentifier()
: UserHandle.USER_SYSTEM;
- pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
- legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
- null /*cpuAbiOverrideString*/, versionCode, pkgFlags, pkgPrivateFlags,
- 0 /*sharedUserAppId*/, usesSdkLibraries, usesSdkLibrariesVersions,
- usesStaticLibraries, usesStaticLibrariesVersions,
- createMimeGroups(mimeGroupNames), domainSetId)
- .setIsPersistent(isPersistent)
+ pkgSetting = new PackageSetting(pkgName, realPkgName, codePath, pkgFlags,
+ pkgPrivateFlags, domainSetId)
+ .setUsesSdkLibraries(usesSdkLibraries)
+ .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions)
+ .setUsesStaticLibraries(usesStaticLibraries)
+ .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions)
+ .setLegacyNativeLibraryPath(legacyNativeLibraryPath)
+ .setPrimaryCpuAbi(primaryCpuAbi)
+ .setSecondaryCpuAbi(secondaryCpuAbi)
+ .setLongVersionCode(versionCode)
+ .setMimeGroups(createMimeGroups(mimeGroupNames))
.setTargetSdkVersion(targetSdkVersion)
- .setRestrictUpdateHash(restrictUpdatedHash);
- pkgSetting.setLastModifiedTime(codePath.lastModified());
+ .setRestrictUpdateHash(restrictUpdatedHash)
+ .setLastModifiedTime(codePath.lastModified());
if (sharedUser != null) {
pkgSetting.setSharedUserAppId(sharedUser.mAppId);
}
@@ -1214,7 +1216,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
int pkgPrivateFlags, @NonNull UserManagerService userManager,
@Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions,
@Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions,
- @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, boolean isPersistent,
+ @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId,
int targetSdkVersion, byte[] restrictUpdatedHash)
throws PackageManagerException {
final String pkgName = pkgSetting.getPackageName();
@@ -1268,7 +1270,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
.setSecondaryCpuAbi(secondaryCpuAbi)
.updateMimeGroups(mimeGroupNames)
.setDomainSetId(domainSetId)
- .setIsPersistent(isPersistent)
.setTargetSdkVersion(targetSdkVersion)
.setRestrictUpdateHash(restrictUpdatedHash);
// Update SDK library dependencies if needed.
@@ -3066,6 +3067,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
+ serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+ if (pkg.getRestrictUpdateHash() != null) {
+ serializer.attributeBytesBase64(null, "restrictUpdateHash",
+ pkg.getRestrictUpdateHash());
+ }
if (pkg.getLegacyNativeLibraryPath() != null) {
serializer.attribute(null, "nativeLibraryPath", pkg.getLegacyNativeLibraryPath());
}
@@ -3129,6 +3135,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
serializer.attributeLongHex(null, "ft", pkg.getLastModifiedTime());
serializer.attributeLongHex(null, "ut", pkg.getLastUpdateTime());
serializer.attributeLong(null, "version", pkg.getVersionCode());
+ serializer.attributeInt(null, "targetSdkVersion", pkg.getTargetSdkVersion());
+ if (pkg.getRestrictUpdateHash() != null) {
+ serializer.attributeBytesBase64(null, "restrictUpdateHash",
+ pkg.getRestrictUpdateHash());
+ }
if (!pkg.hasSharedUser()) {
serializer.attributeInt(null, "userId", pkg.getAppId());
} else {
@@ -3165,8 +3176,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
if (pkg.getVolumeUuid() != null) {
serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid());
}
- serializer.attributeBoolean(null, "defaultToDeviceProtectedStorage",
- pkg.isDefaultToDeviceProtectedStorage());
if (pkg.getCategoryOverride() != ApplicationInfo.CATEGORY_UNDEFINED) {
serializer.attributeInt(null, "categoryHint", pkg.getCategoryOverride());
}
@@ -3861,6 +3870,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
}
long versionCode = parser.getAttributeLong(null, "version", 0);
+ int targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+ byte[] restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash",
+ null);
int pkgFlags = 0;
int pkgPrivateFlags = 0;
@@ -3873,10 +3885,15 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
// debug invalid entries. The actual logic for migrating to a new ID is done in other
// methods that use DomainVerificationManagerInternal#generateNewId
UUID domainSetId = DomainVerificationManagerInternal.DISABLED_ID;
- PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
- legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr,
- versionCode, pkgFlags, pkgPrivateFlags, 0 /*sharedUserAppId*/, null, null, null,
- null, null, domainSetId);
+ PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), pkgFlags,
+ pkgPrivateFlags, domainSetId)
+ .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+ .setPrimaryCpuAbi(primaryCpuAbiStr)
+ .setSecondaryCpuAbi(secondaryCpuAbiStr)
+ .setCpuAbiOverride(cpuAbiOverrideStr)
+ .setLongVersionCode(versionCode)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdateHash);
long timeStamp = parser.getAttributeLongHex(null, "ft", 0);
if (timeStamp == 0) {
timeStamp = parser.getAttributeLong(null, "ts", 0);
@@ -3955,7 +3972,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
String installInitiatingPackageName = null;
boolean installInitiatorUninstalled = false;
String volumeUuid = null;
- boolean defaultToDeviceProtectedStorage = false;
boolean updateAvailable = false;
int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
int pkgFlags = 0;
@@ -3970,6 +3986,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
long loadingCompletedTime = 0;
UUID domainSetId;
String appMetadataFilePath = null;
+ int targetSdkVersion = 0;
+ byte[] restrictUpdateHash = null;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -3993,6 +4011,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
}
versionCode = parser.getAttributeLong(null, "version", 0);
+ targetSdkVersion = parser.getAttributeInt(null, "targetSdkVersion", 0);
+ restrictUpdateHash = parser.getAttributeBytesBase64(null, "restrictUpdateHash", null);
installerPackageName = parser.getAttributeValue(null, "installer");
installerPackageUid = parser.getAttributeInt(null, "installerUid", INVALID_UID);
updateOwnerPackageName = parser.getAttributeValue(null, "updateOwner");
@@ -4005,8 +4025,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
installInitiatorUninstalled = parser.getAttributeBoolean(null,
"installInitiatorUninstalled", false);
volumeUuid = parser.getAttributeValue(null, "volumeUuid");
- defaultToDeviceProtectedStorage = parser.getAttributeBoolean(
- null, "defaultToDeviceProtectedStorage", false);
categoryHint = parser.getAttributeInt(null, "categoryHint",
ApplicationInfo.CATEGORY_UNDEFINED);
appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
@@ -4088,11 +4106,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
+ parser.getPositionDescription());
} else if (appId > 0) {
packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
- legacyNativeLibraryPathStr, primaryCpuAbiString, secondaryCpuAbiString,
- cpuAbiOverrideString, appId, versionCode, pkgFlags, pkgPrivateFlags,
- null /* usesSdkLibraries */, null /* usesSdkLibraryVersions */,
- null /* usesStaticLibraries */, null /* usesStaticLibraryVersions */,
- null /* mimeGroups */, domainSetId);
+ appId, pkgFlags, pkgPrivateFlags, domainSetId);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name + ": appId="
+ appId + " pkg=" + packageSetting);
@@ -4101,22 +4115,26 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
+ appId + " while parsing settings at "
+ parser.getPositionDescription());
} else {
+ packageSetting.setLegacyNativeLibraryPath(legacyNativeLibraryPathStr);
+ packageSetting.setPrimaryCpuAbi(primaryCpuAbiString);
+ packageSetting.setSecondaryCpuAbi(secondaryCpuAbiString);
+ packageSetting.setCpuAbiOverride(cpuAbiOverrideString);
+ packageSetting.setLongVersionCode(versionCode);
packageSetting.setLastModifiedTime(timeStamp);
packageSetting.setLastUpdateTime(lastUpdateTime);
}
} else if (sharedUserAppId != 0) {
if (sharedUserAppId > 0) {
packageSetting = new PackageSetting(name.intern(), realName,
- new File(codePathStr), legacyNativeLibraryPathStr,
- primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
- versionCode, pkgFlags, pkgPrivateFlags, sharedUserAppId,
- null /* usesSdkLibraries */,
- null /* usesSdkLibrariesVersions */,
- null /* usesStaticLibraries */,
- null /* usesStaticLibraryVersions */,
- null /* mimeGroups */, domainSetId);
- packageSetting.setLastModifiedTime(timeStamp);
- packageSetting.setLastUpdateTime(lastUpdateTime);
+ new File(codePathStr), pkgFlags, pkgPrivateFlags, domainSetId)
+ .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
+ .setPrimaryCpuAbi(primaryCpuAbiString)
+ .setSecondaryCpuAbi(secondaryCpuAbiString)
+ .setCpuAbiOverride(cpuAbiOverrideString)
+ .setLongVersionCode(versionCode)
+ .setSharedUserAppId(sharedUserAppId)
+ .setLastModifiedTime(timeStamp)
+ .setLastUpdateTime(lastUpdateTime);
mPendingPackages.add(packageSetting);
if (PackageManagerService.DEBUG_SETTINGS)
Log.i(PackageManagerService.TAG, "Reading package " + name
@@ -4146,7 +4164,6 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
installInitiatorUninstalled);
packageSetting.setInstallSource(installSource)
.setVolumeUuid(volumeUuid)
- .setDefaultToDeviceProtectedStorage(defaultToDeviceProtectedStorage)
.setCategoryOverride(categoryHint)
.setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
.setPrimaryCpuAbi(primaryCpuAbiString)
@@ -4155,7 +4172,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
.setForceQueryableOverride(installedForceQueryable)
.setLoadingProgress(loadingProgress)
.setLoadingCompletedTime(loadingCompletedTime)
- .setAppMetadataFilePath(appMetadataFilePath);
+ .setAppMetadataFilePath(appMetadataFilePath)
+ .setTargetSdkVersion(targetSdkVersion)
+ .setRestrictUpdateHash(restrictUpdateHash);
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
@@ -4916,9 +4935,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
}
pw.print(prefix); pw.print(" versionCode="); pw.print(ps.getVersionCode());
if (pkg != null) {
- pw.print(" minSdk="); pw.print(pkg.getMinSdkVersion());
- pw.print(" targetSdk="); pw.println(pkg.getTargetSdkVersion());
-
+ pw.print(" minSdk=");
+ pw.print(pkg.getMinSdkVersion());
+ }
+ pw.print(" targetSdk="); pw.println(ps.getTargetSdkVersion());
+ if (pkg != null) {
SparseIntArray minExtensionVersions = pkg.getMinExtensionVersions();
pw.print(prefix); pw.print(" minExtensionVersions=[");
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 99878679431c..585e2e48fb85 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.Flags.sdkLibIndependence;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST;
@@ -951,10 +952,12 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
+ // Allow installation even if sdk-library dependency doesn't exist
+ boolean required = !sdkLibIndependence();
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
- pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos,
- availablePackages, newLibraries);
+ pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(),
+ usesLibraryInfos, availablePackages, newLibraries);
}
return usesLibraryInfos;
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index 29d99a73a034..e8cebefb8631 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -120,8 +120,8 @@ public final class SuspendPackageHelper {
return packageNames;
}
- final SuspendParams newSuspendParams =
- new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined);
+ final SuspendParams newSuspendParams = suspended
+ ? new SuspendParams(dialogInfo, appExtras, launcherExtras, quarantined) : null;
final List<String> unmodifiablePackages = new ArrayList<>(packageNames.length);
@@ -156,8 +156,8 @@ public final class SuspendPackageHelper {
final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
packageState.getUserStateOrDefault(userId).getSuspendParams();
- SuspendParams oldSuspendParams = suspendParamsMap == null
- ? null : suspendParamsMap.get(packageName);
+ final SuspendParams oldSuspendParams = suspendParamsMap == null
+ ? null : suspendParamsMap.get(callingPackage);
boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
if (suspended && !changed) {
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 04cd183327fb..0e7ce2efc8b8 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
import android.graphics.Bitmap;
@@ -407,6 +408,11 @@ public abstract class UserManagerInternal {
public abstract @NonNull UserInfo[] getUserInfos();
/**
+ * Gets a {@link LauncherUserInfo} for the given {@code userId}, or {@code null} if not found.
+ */
+ public abstract @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId);
+
+ /**
* Sets all default cross profile intent filters between {@code parentUserId} and
* {@code profileUserId}.
*/
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7331bc141ec2..154ee6eda138 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -62,6 +62,7 @@ import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
+import android.content.pm.LauncherUserInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -7153,6 +7154,24 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public @Nullable LauncherUserInfo getLauncherUserInfo(@UserIdInt int userId) {
+ UserInfo userInfo;
+ synchronized (mUsersLock) {
+ userInfo = getUserInfoLU(userId);
+ }
+ if (userInfo != null) {
+ final UserTypeDetails userDetails = getUserTypeDetails(userInfo);
+ final LauncherUserInfo uiInfo = new LauncherUserInfo.Builder(
+ userDetails.getName(),
+ userInfo.serialNumber)
+ .build();
+ return uiInfo;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
int state;
synchronized (mUserStates) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 61e96ca3dd59..2ad8bcf4739e 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -1016,8 +1016,8 @@ public class PackageInfoUtils {
return;
}
- if (android.content.pm.Flags.nullableDataDir()
- && !state.isInstalled() && !state.dataExists()) {
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The data dir has been deleted
output.dataDir = null;
return;
@@ -1065,8 +1065,8 @@ public class PackageInfoUtils {
return;
}
- if (android.content.pm.Flags.nullableDataDir()
- && !state.isInstalled() && !state.dataExists()) {
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The data dir has been deleted
output.dataDir = null;
return;
@@ -1113,9 +1113,9 @@ public class PackageInfoUtils {
return Environment.getDataSystemDirectory();
}
- if (android.content.pm.Flags.nullableDataDir()
- && !ps.getUserStateOrDefault(userId).isInstalled()
- && !ps.getUserStateOrDefault(userId).dataExists()) {
+ if (!ps.getUserStateOrDefault(userId).isInstalled()
+ && !ps.getUserStateOrDefault(userId).dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
// The app has been uninstalled for the user and the data dir has been deleted
return null;
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 46121dcd9dae..8240c47a607b 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -18,7 +18,7 @@ package com.android.server.pm.pkg.parsing;
import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static android.content.pm.Flags.preventSdkLibApp;
+import static android.content.pm.Flags.disallowSdkLibsToBeApps;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -404,7 +404,7 @@ public class ParsingPackageUtils {
try {
final File baseApk = new File(lite.getBaseApkPath());
- boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp();
+ boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
lite.getPath(), assetLoader, flags, shouldSkipComponents);
if (result.isError()) {
@@ -458,7 +458,7 @@ public class ParsingPackageUtils {
final PackageLite lite = liteResult.getResult();
final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
try {
- boolean shouldSkipComponents = lite.isIsSdkLibrary() && preventSdkLibApp();
+ boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();
final ParseResult<ParsingPackage> result = parseBaseApk(input,
apkFile,
apkFile.getCanonicalPath(),
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index d6e35e8b1fd3..a33e3532153b 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -347,8 +347,15 @@ public final class PermissionPolicyService extends SystemService {
UserHandle user = UserHandle.getUserHandleForUid(uid);
PermissionControllerManager manager = mPermControllerManagers.get(user);
if (manager == null) {
- manager = new PermissionControllerManager(
- getUserContext(getContext(), user), PermissionThread.getHandler());
+ try {
+ manager = new PermissionControllerManager(
+ getUserContext(getContext(), user), PermissionThread.getHandler());
+ } catch (IllegalArgumentException exception) {
+ // There's a possible race condition when a user is being removed
+ Log.e(LOG_TAG, "Could not create PermissionControllerManager for user"
+ + user, exception);
+ return;
+ }
mPermControllerManagers.put(user, manager);
}
manager.updateUserSensitiveForApp(uid);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3a6664a72439..b4396818c43c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,6 +139,7 @@ import android.os.DeviceIdleManager;
import android.os.FactoryTest;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeReason;
@@ -715,6 +716,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private static final int MSG_LOG_KEYBOARD_SYSTEM_EVENT = 26;
private class PolicyHandler extends Handler {
+
+ private PolicyHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -2166,10 +2172,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static class Injector {
private final Context mContext;
private final WindowManagerFuncs mWindowManagerFuncs;
+ private final Looper mLooper;
- Injector(Context context, WindowManagerFuncs funcs) {
+ Injector(Context context, WindowManagerFuncs funcs, Looper looper) {
mContext = context;
mWindowManagerFuncs = funcs;
+ mLooper = looper;
}
Context getContext() {
@@ -2180,6 +2188,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return mWindowManagerFuncs;
}
+ Looper getLooper() {
+ return mLooper;
+ }
+
AccessibilityShortcutController getAccessibilityShortcutController(
Context context, Handler handler, int initialUserId) {
return new AccessibilityShortcutController(context, handler, initialUserId);
@@ -2208,7 +2220,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
public void init(Context context, WindowManagerFuncs funcs) {
- init(new Injector(context, funcs));
+ init(new Injector(context, funcs, Looper.myLooper()));
}
@VisibleForTesting
@@ -2284,7 +2296,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mContext, minHorizontal, maxHorizontal, minVertical, maxVertical, maxRadius);
}
- mHandler = new PolicyHandler();
+ mHandler = new PolicyHandler(injector.getLooper());
mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mSettingsObserver.observe();
@@ -2466,7 +2478,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
initKeyCombinationRules();
- initSingleKeyGestureRules();
+ initSingleKeyGestureRules(injector.getLooper());
mSideFpsEventHandler = new SideFpsEventHandler(mContext, mHandler, mPowerManager);
}
@@ -2737,8 +2749,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- private void initSingleKeyGestureRules() {
- mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext);
+ private void initSingleKeyGestureRules(Looper looper) {
+ mSingleKeyGestureDetector = SingleKeyGestureDetector.get(mContext, looper);
mSingleKeyGestureDetector.addRule(new PowerKeyRule());
if (hasLongPressOnBackBehavior()) {
mSingleKeyGestureDetector.addRule(new BackKeyRule());
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 5fc0637debea..047555ae491c 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -40,6 +40,7 @@ public final class SingleKeyGestureDetector {
private static final int MSG_KEY_LONG_PRESS = 0;
private static final int MSG_KEY_VERY_LONG_PRESS = 1;
private static final int MSG_KEY_DELAYED_PRESS = 2;
+ private static final int MSG_KEY_UP = 3;
private int mKeyPressCounter;
private boolean mBeganFromNonInteractive = false;
@@ -144,6 +145,13 @@ public final class SingleKeyGestureDetector {
* Callback when very long press has been detected.
*/
void onVeryLongPress(long eventTime) {}
+ /**
+ * Callback executed upon each key up event that hasn't been processed by long press.
+ *
+ * @param eventTime the timestamp of this event.
+ * @param pressCount the number of presses detected leading up to this key up event.
+ */
+ void onKeyUp(long eventTime, int pressCount) {}
@Override
public String toString() {
@@ -171,8 +179,8 @@ public final class SingleKeyGestureDetector {
}
}
- static SingleKeyGestureDetector get(Context context) {
- SingleKeyGestureDetector detector = new SingleKeyGestureDetector();
+ static SingleKeyGestureDetector get(Context context, Looper looper) {
+ SingleKeyGestureDetector detector = new SingleKeyGestureDetector(looper);
sDefaultLongPressTimeout = context.getResources().getInteger(
com.android.internal.R.integer.config_globalActionsKeyTimeout);
sDefaultVeryLongPressTimeout = context.getResources().getInteger(
@@ -180,8 +188,8 @@ public final class SingleKeyGestureDetector {
return detector;
}
- private SingleKeyGestureDetector() {
- mHandler = new KeyHandler();
+ private SingleKeyGestureDetector(Looper looper) {
+ mHandler = new KeyHandler(looper);
}
void addRule(SingleKeyRule rule) {
@@ -330,6 +338,13 @@ public final class SingleKeyGestureDetector {
}
if (event.getKeyCode() == mActiveRule.mKeyCode) {
+ // key-up action should always be triggered if not processed by long press.
+ Message msgKeyUp =
+ mHandler.obtainMessage(
+ MSG_KEY_UP, mActiveRule.mKeyCode, mKeyPressCounter, mActiveRule);
+ msgKeyUp.setAsynchronous(true);
+ mHandler.sendMessage(msgKeyUp);
+
// Directly trigger short press when max count is 1.
if (mActiveRule.getMaxMultiPressCount() == 1) {
if (DEBUG) {
@@ -402,8 +417,8 @@ public final class SingleKeyGestureDetector {
}
private class KeyHandler extends Handler {
- KeyHandler() {
- super(Looper.myLooper());
+ KeyHandler(Looper looper) {
+ super(looper);
}
@Override
@@ -417,6 +432,12 @@ public final class SingleKeyGestureDetector {
final int keyCode = msg.arg1;
final int pressCount = msg.arg2;
switch(msg.what) {
+ case MSG_KEY_UP:
+ if (DEBUG) {
+ Log.i(TAG, "Detect key up " + KeyEvent.keyCodeToString(keyCode));
+ }
+ rule.onKeyUp(mLastDownTime, pressCount);
+ break;
case MSG_KEY_LONG_PRESS:
if (DEBUG) {
Log.i(TAG, "Detect long press " + KeyEvent.keyCodeToString(keyCode));
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 1d5cac54d12b..7f55836598b5 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -833,18 +833,24 @@ final class VibrationSettings {
private final SparseArray<Integer> mProcStatesCache = new SparseArray<>();
public boolean isUidForeground(int uid) {
- return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
- <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ synchronized (this) {
+ return mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
+ <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ }
}
@Override
public void onUidGone(int uid, boolean disabled) {
- mProcStatesCache.delete(uid);
+ synchronized (this) {
+ mProcStatesCache.delete(uid);
+ }
}
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
- mProcStatesCache.put(uid, procState);
+ synchronized (this) {
+ mProcStatesCache.put(uid, procState);
+ }
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index c54e3bdf0d51..5f8bbe5f18ad 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -29,7 +29,6 @@ import static com.android.server.wallpaper.WallpaperUtils.makeWallpaperIdLocked;
import android.annotation.Nullable;
import android.app.WallpaperColors;
-import android.app.WallpaperManager;
import android.app.WallpaperManager.SetWallpaperFlags;
import android.app.backup.WallpaperBackupHelper;
import android.content.ComponentName;
@@ -38,7 +37,6 @@ import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.FileUtils;
-import android.os.SystemProperties;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -77,8 +75,6 @@ class WallpaperDataParser {
private final WallpaperCropper mWallpaperCropper;
private final Context mContext;
- private final boolean mIsLockscreenLiveWallpaperEnabled;
-
WallpaperDataParser(Context context, WallpaperDisplayHelper wallpaperDisplayHelper,
WallpaperCropper wallpaperCropper) {
mContext = context;
@@ -86,8 +82,6 @@ class WallpaperDataParser {
mWallpaperCropper = wallpaperCropper;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
- mIsLockscreenLiveWallpaperEnabled =
- SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
}
private JournaledFile makeJournaledFile(int userId) {
@@ -127,42 +121,26 @@ class WallpaperDataParser {
}
/**
- * TODO(b/197814683) adapt comment once flag is removed
- *
* Load the system wallpaper (and the lock wallpaper, if it exists) from disk
* @param userId the id of the user for which the wallpaper should be loaded
* @param keepDimensionHints if false, parse and set the
* {@link DisplayData} width and height for the specified userId
- * @param wallpaper the wallpaper object to reuse to do the modifications.
- * If null, a new object will be created.
- * @param lockWallpaper the lock wallpaper object to reuse to do the modifications.
- * If null, a new object will be created.
- * @param which The wallpaper(s) to load. Only has effect if
- * {@link WallpaperManager#isLockscreenLiveWallpaperEnabled} is true,
- * otherwise both wallpaper will always be loaded.
+ * @param migrateFromOld whether the current wallpaper is pre-N and needs migration
+ * @param which The wallpaper(s) to load.
* @return a {@link WallpaperLoadingResult} object containing the wallpaper data.
- * This object will contain the {@code wallpaper} and
- * {@code lockWallpaper} provided as parameters, if they are not null.
*/
public WallpaperLoadingResult loadSettingsLocked(int userId, boolean keepDimensionHints,
- WallpaperData wallpaper, WallpaperData lockWallpaper, @SetWallpaperFlags int which) {
+ boolean migrateFromOld, @SetWallpaperFlags int which) {
JournaledFile journal = makeJournaledFile(userId);
FileInputStream stream = null;
File file = journal.chooseForRead();
- boolean migrateFromOld = wallpaper == null;
-
- boolean separateLockscreenEngine = mIsLockscreenLiveWallpaperEnabled;
- boolean loadSystem = !separateLockscreenEngine || (which & FLAG_SYSTEM) != 0;
- boolean loadLock = !separateLockscreenEngine || (which & FLAG_LOCK) != 0;
-
- // don't reuse the wallpaper objects in the new version
- if (separateLockscreenEngine) {
- wallpaper = null;
- lockWallpaper = null;
- }
+ boolean loadSystem = (which & FLAG_SYSTEM) != 0;
+ boolean loadLock = (which & FLAG_LOCK) != 0;
+ WallpaperData wallpaper = null;
+ WallpaperData lockWallpaper = null;
- if (wallpaper == null && loadSystem) {
+ if (loadSystem) {
// Do this once per boot
if (migrateFromOld) migrateFromOld();
wallpaper = new WallpaperData(userId, FLAG_SYSTEM);
@@ -188,11 +166,8 @@ class WallpaperDataParser {
type = parser.next();
if (type == XmlPullParser.START_TAG) {
String tag = parser.getName();
- if (("wp".equals(tag) && loadSystem)
- || ("kwp".equals(tag) && mIsLockscreenLiveWallpaperEnabled
- && loadLock)) {
-
- if ("kwp".equals(tag) && lockWallpaper == null) {
+ if (("wp".equals(tag) && loadSystem) || ("kwp".equals(tag) && loadLock)) {
+ if ("kwp".equals(tag)) {
lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
}
WallpaperData wallpaperToParse =
@@ -219,12 +194,6 @@ class WallpaperDataParser {
Slog.v(TAG, "mNextWallpaperComponent:"
+ wallpaper.nextWallpaperComponent);
}
- } else if ("kwp".equals(tag) && !mIsLockscreenLiveWallpaperEnabled) {
- // keyguard-specific wallpaper for this user (legacy code)
- if (lockWallpaper == null) {
- lockWallpaper = new WallpaperData(userId, FLAG_LOCK);
- }
- parseWallpaperAttributes(parser, lockWallpaper, false);
}
}
} while (type != XmlPullParser.END_DOCUMENT);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index c7a3c4349f4c..bdcde66a8102 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -188,8 +188,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
private final Object mLock = new Object();
- /** True to enable a second engine for lock screen wallpaper when different from system wp. */
- private final boolean mIsLockscreenLiveWallpaperEnabled;
/** True to support different crops for different display dimensions */
private final boolean mIsMultiCropEnabled;
/** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
@@ -230,7 +228,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
}
- WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+ WallpaperData dataForEvent(boolean lockChanged) {
WallpaperData wallpaper = null;
synchronized (mLock) {
if (lockChanged) {
@@ -252,7 +250,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final File changedFile = new File(mWallpaperDir, path);
final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
- final WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
+ final WallpaperData wallpaper = dataForEvent(lockWallpaperChanged);
final boolean moved = (event == MOVED_TO);
final boolean written = (event == CLOSE_WRITE || moved);
@@ -378,7 +376,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
saveSettingsLocked(wallpaper.userId);
- if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+ if (localSync != null) {
localSync.complete();
}
}
@@ -389,129 +387,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- // Handles static wallpaper changes generated by WallpaperObserver events when
- // enableSeparateLockScreenEngine() is false.
- // TODO(b/266818039) Remove this method
- private void updateWallpapersLegacy(int event, String path) {
- final boolean moved = (event == MOVED_TO);
- final boolean written = (event == CLOSE_WRITE || moved);
- final File changedFile = new File(mWallpaperDir, path);
-
- // System and system+lock changes happen on the system wallpaper input file;
- // lock-only changes happen on the dedicated lock wallpaper input file
- final boolean sysWallpaperChanged = (mWallpaperFile.equals(changedFile));
- final boolean lockWallpaperChanged = (mWallpaperLockFile.equals(changedFile));
- int notifyColorsWhich = 0;
- WallpaperData wallpaper = dataForEvent(sysWallpaperChanged, lockWallpaperChanged);
-
- if (DEBUG) {
- Slog.v(TAG, "Wallpaper file change: evt=" + event
- + " path=" + path
- + " sys=" + sysWallpaperChanged
- + " lock=" + lockWallpaperChanged
- + " imagePending=" + wallpaper.imageWallpaperPending
- + " mWhich=0x" + Integer.toHexString(wallpaper.mWhich)
- + " written=" + written);
- }
-
- if (moved && lockWallpaperChanged) {
- // We just migrated sys -> lock to preserve imagery for an impending
- // new system-only wallpaper. Tell keyguard about it and make sure it
- // has the right SELinux label.
- if (DEBUG) {
- Slog.i(TAG, "Sys -> lock MOVED_TO");
- }
- SELinux.restorecon(changedFile);
- notifyLockWallpaperChanged();
- notifyWallpaperColorsChanged(wallpaper, FLAG_LOCK);
- return;
- }
-
- synchronized (mLock) {
- if (sysWallpaperChanged || lockWallpaperChanged) {
- notifyCallbacksLocked(wallpaper);
- if (wallpaper.wallpaperComponent == null
- || event != CLOSE_WRITE // includes the MOVED_TO case
- || wallpaper.imageWallpaperPending) {
- if (written) {
- // The image source has finished writing the source image,
- // so we now produce the crop rect (in the background), and
- // only publish the new displayable (sub)image as a result
- // of that work.
- if (DEBUG) {
- Slog.v(TAG, "Wallpaper written; generating crop");
- }
- SELinux.restorecon(changedFile);
- if (moved) {
- // This is a restore, so generate the crop using any just-restored new
- // crop guidelines, making sure to preserve our local dimension hints.
- // We also make sure to reapply the correct SELinux label.
- if (DEBUG) {
- Slog.v(TAG, "moved-to, therefore restore; reloading metadata");
- }
- loadSettingsLocked(wallpaper.userId, true, FLAG_SYSTEM | FLAG_LOCK);
- }
- mWallpaperCropper.generateCrop(wallpaper);
- if (DEBUG) {
- Slog.v(TAG, "Crop done; invoking completion callback");
- }
- wallpaper.imageWallpaperPending = false;
- if (sysWallpaperChanged) {
- IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- Slog.d(TAG, "publish system wallpaper changed!");
- notifyWallpaperChanged(wallpaper);
- }
- };
- // If this was the system wallpaper, rebind...
- bindWallpaperComponentLocked(mImageWallpaper, true,
- false, wallpaper, callback);
- notifyColorsWhich |= FLAG_SYSTEM;
- }
- if (lockWallpaperChanged
- || (wallpaper.mWhich & FLAG_LOCK) != 0) {
- if (DEBUG) {
- Slog.i(TAG, "Lock-relevant wallpaper changed");
- }
- // either a lock-only wallpaper commit or a system+lock event.
- // if it's system-plus-lock we need to wipe the lock bookkeeping;
- // we're falling back to displaying the system wallpaper there.
- if (!lockWallpaperChanged) {
- mLockWallpaperMap.remove(wallpaper.userId);
- }
- // and in any case, tell keyguard about it
- notifyLockWallpaperChanged();
- notifyColorsWhich |= FLAG_LOCK;
- }
-
- saveSettingsLocked(wallpaper.userId);
- // Notify the client immediately if only lockscreen wallpaper changed.
- if (lockWallpaperChanged && !sysWallpaperChanged) {
- notifyWallpaperChanged(wallpaper);
- }
- }
- }
- }
- }
-
- // Outside of the lock since it will synchronize itself
- if (notifyColorsWhich != 0) {
- notifyWallpaperColorsChanged(wallpaper, notifyColorsWhich);
- }
- }
-
@Override
public void onEvent(int event, String path) {
- if (path == null) {
- return;
- }
-
- if (mIsLockscreenLiveWallpaperEnabled) {
- updateWallpapers(event, path);
- } else {
- updateWallpapersLegacy(event, path);
- }
+ if (path != null) updateWallpapers(event, path);
}
}
@@ -528,17 +406,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private void notifyLockWallpaperChanged() {
- final IWallpaperManagerCallback cb = mKeyguardListener;
- if (cb != null) {
- try {
- cb.onWallpaperChanged();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify keyguard callback about wallpaper changes", e);
- }
- }
- }
-
void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
if (DEBUG) {
Slog.i(TAG, "Notifying wallpaper colors changed");
@@ -597,14 +464,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private void notifyColorListeners(@NonNull WallpaperColors wallpaperColors, int which,
int userId, int displayId) {
- final IWallpaperManagerCallback keyguardListener;
final ArrayList<IWallpaperManagerCallback> colorListeners = new ArrayList<>();
synchronized (mLock) {
final RemoteCallbackList<IWallpaperManagerCallback> currentUserColorListeners =
getWallpaperCallbacks(userId, displayId);
final RemoteCallbackList<IWallpaperManagerCallback> userAllColorListeners =
getWallpaperCallbacks(UserHandle.USER_ALL, displayId);
- keyguardListener = mKeyguardListener;
if (currentUserColorListeners != null) {
final int count = currentUserColorListeners.beginBroadcast();
@@ -633,15 +498,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, "onWallpaperColorsChanged() threw an exception", e);
}
}
-
- // Only shows Keyguard on default display
- if (keyguardListener != null && displayId == DEFAULT_DISPLAY) {
- try {
- keyguardListener.onWallpaperColorsChanged(wallpaperColors, which, userId);
- } catch (RemoteException e) {
- Slog.w(TAG, "keyguardListener.onWallpaperColorsChanged threw an exception", e);
- }
- }
}
/**
@@ -762,8 +618,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private final MyPackageMonitor mMonitor;
private final AppOpsManager mAppOpsManager;
- // TODO("b/264637309") probably move this in WallpaperDisplayUtils,
- // after logic is changed for the lockscreen lwp project
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@@ -814,8 +668,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
protected WallpaperData mLastWallpaper;
// The currently bound lock screen only wallpaper, or null if none
protected WallpaperData mLastLockWallpaper;
- private IWallpaperManagerCallback mKeyguardListener;
- private boolean mWaitingForUnlock;
/**
* Flag set to true after reboot if the home wallpaper is waiting for the device to be unlocked.
@@ -1017,8 +869,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) {
Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent
+ ", reverting to built-in wallpaper!");
- int which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
- clearWallpaperLocked(which, mWallpaper.userId, null);
+ int which = mWallpaper.mWhich;
+ clearWallpaperLocked(which, mWallpaper.userId, false, null);
}
}
};
@@ -1198,7 +1050,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
} else {
// Timeout
Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, null);
+ clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null);
final String flattened = wpService.flattenToString();
EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
flattened.substring(0, Math.min(flattened.length(),
@@ -1238,7 +1090,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mLmkLimitRebindRetries <= 0) {
Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!");
clearWallpaperLocked(
- mWallpaper.mWhich, mWallpaper.userId, null);
+ mWallpaper.mWhich, mWallpaper.userId, false, null);
mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
return;
}
@@ -1257,7 +1109,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
&& mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
> SystemClock.uptimeMillis()) {
Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, null);
+ clearWallpaperLocked(FLAG_SYSTEM, mWallpaper.userId, false, null);
} else {
mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
tryToRebind();
@@ -1294,19 +1146,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mImageWallpaper.equals(mWallpaper.wallpaperComponent)) {
return;
}
-
- // Live wallpapers always are system wallpapers unless lock screen live wp is
- // enabled.
- which = mIsLockscreenLiveWallpaperEnabled ? mWallpaper.mWhich : FLAG_SYSTEM;
+ which = mWallpaper.mWhich;
mWallpaper.primaryColors = primaryColors;
-
- // It's also the lock screen wallpaper when we don't have a bitmap in there.
- if (displayId == DEFAULT_DISPLAY) {
- final WallpaperData lockedWallpaper = mLockWallpaperMap.get(mWallpaper.userId);
- if (lockedWallpaper == null) {
- which |= FLAG_LOCK;
- }
- }
}
if (which != 0) {
notifyWallpaperColorsChangedOnDisplay(mWallpaper, which, displayId);
@@ -1492,9 +1333,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
wallpaper, null)) {
Slog.w(TAG, "Wallpaper " + wpService
+ " no longer available; reverting to default");
- int which = mIsLockscreenLiveWallpaperEnabled
- ? wallpaper.mWhich : FLAG_SYSTEM;
- clearWallpaperLocked(which, wallpaper.userId, null);
+ clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
}
}
@@ -1568,7 +1407,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
boolean changed = false;
- int which = mIsLockscreenLiveWallpaperEnabled ? wallpaper.mWhich : FLAG_SYSTEM;
if (wallpaper.wallpaperComponent != null) {
int change = isPackageDisappearing(wallpaper.wallpaperComponent
.getPackageName());
@@ -1578,7 +1416,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (doit) {
Slog.w(TAG, "Wallpaper uninstalled, removing: "
+ wallpaper.wallpaperComponent);
- clearWallpaperLocked(which, wallpaper.userId, null);
+ clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
}
}
@@ -1599,7 +1437,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
} catch (NameNotFoundException e) {
Slog.w(TAG, "Wallpaper component gone, removing: "
+ wallpaper.wallpaperComponent);
- clearWallpaperLocked(which, wallpaper.userId, null);
+ clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
}
if (wallpaper.nextWallpaperComponent != null
@@ -1686,9 +1524,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mColorsChangedListeners = new SparseArray<>();
mWallpaperDataParser = new WallpaperDataParser(mContext, mWallpaperDisplayHelper,
mWallpaperCropper);
-
- mIsLockscreenLiveWallpaperEnabled =
- SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
mIsMultiCropEnabled =
SystemProperties.getBoolean("persist.wm.debug.wallpaper_multi_crop", false);
LocalServices.addService(WallpaperManagerInternal.class, new LocalService());
@@ -1755,8 +1590,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) {
Slog.i(TAG, "Unable to regenerate crop; resetting");
}
- int which = isLockscreenLiveWallpaperEnabled() ? wallpaper.mWhich : FLAG_SYSTEM;
- clearWallpaperLocked(which, UserHandle.USER_SYSTEM, null);
+ clearWallpaperLocked(wallpaper.mWhich, UserHandle.USER_SYSTEM, false, null);
}
} else {
if (DEBUG) {
@@ -1883,29 +1717,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public void onUnlockUser(final int userId) {
synchronized (mLock) {
if (mCurrentUserId == userId) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- if (mHomeWallpaperWaitingForUnlock) {
- final WallpaperData systemWallpaper =
- getWallpaperSafeLocked(userId, FLAG_SYSTEM);
- switchWallpaper(systemWallpaper, null);
- // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
- notifyCallbacksLocked(systemWallpaper);
- }
- if (mLockWallpaperWaitingForUnlock) {
- final WallpaperData lockWallpaper =
- getWallpaperSafeLocked(userId, FLAG_LOCK);
- switchWallpaper(lockWallpaper, null);
- notifyCallbacksLocked(lockWallpaper);
- }
- }
-
- if (mWaitingForUnlock && !mIsLockscreenLiveWallpaperEnabled) {
- // the desired wallpaper is not direct-boot aware, load it now
+ if (mHomeWallpaperWaitingForUnlock) {
final WallpaperData systemWallpaper =
getWallpaperSafeLocked(userId, FLAG_SYSTEM);
switchWallpaper(systemWallpaper, null);
+ // TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
notifyCallbacksLocked(systemWallpaper);
}
+ if (mLockWallpaperWaitingForUnlock) {
+ final WallpaperData lockWallpaper =
+ getWallpaperSafeLocked(userId, FLAG_LOCK);
+ switchWallpaper(lockWallpaper, null);
+ notifyCallbacksLocked(lockWallpaper);
+ }
// Make sure that the SELinux labeling of all the relevant files is correct.
// This corrects for mislabeling bugs that might have arisen from move-to
@@ -1954,21 +1778,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
mCurrentUserId = userId;
systemWallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
-
- if (mIsLockscreenLiveWallpaperEnabled) {
- lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)
- ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK);
- } else {
- final WallpaperData tmpLockWallpaper = mLockWallpaperMap.get(userId);
- lockWallpaper = tmpLockWallpaper == null ? systemWallpaper : tmpLockWallpaper;
- }
+ lockWallpaper = systemWallpaper.mWhich == (FLAG_LOCK | FLAG_SYSTEM)
+ ? systemWallpaper : getWallpaperSafeLocked(userId, FLAG_LOCK);
// Not started watching yet, in case wallpaper data was loaded for other reasons.
if (systemWallpaper.wallpaperObserver == null) {
systemWallpaper.wallpaperObserver = new WallpaperObserver(systemWallpaper);
systemWallpaper.wallpaperObserver.startWatching();
}
- if (mIsLockscreenLiveWallpaperEnabled && lockWallpaper != systemWallpaper) {
+ if (lockWallpaper != systemWallpaper) {
switchWallpaper(lockWallpaper, null);
}
switchWallpaper(systemWallpaper, reply);
@@ -1988,11 +1806,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
synchronized (mLock) {
- mWaitingForUnlock = false;
- if (mIsLockscreenLiveWallpaperEnabled) {
- if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
- if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
- }
+ if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
+ if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
final ComponentName cname = wallpaper.wallpaperComponent != null ?
wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
@@ -2006,37 +1821,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
} catch (RemoteException e) {
Slog.w(TAG, "Failure starting previous wallpaper; clearing", e);
}
-
- if (mIsLockscreenLiveWallpaperEnabled) {
- onSwitchWallpaperFailLocked(wallpaper, reply, si);
- return;
- }
-
- if (si == null) {
- clearWallpaperLocked(FLAG_SYSTEM, wallpaper.userId, reply);
- } else {
- Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
- // We might end up persisting the current wallpaper data
- // while locked, so pretend like the component was actually
- // bound into place
- wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent;
- final WallpaperData fallback = new WallpaperData(wallpaper.userId, FLAG_LOCK);
- bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
- mWaitingForUnlock = true;
- }
+ onSwitchWallpaperFailLocked(wallpaper, reply, si);
}
}
}
/**
* Fallback method if a wallpaper fails to load on boot or after a user switch.
- * Only called if mIsLockscreenLiveWallpaperEnabled is true.
*/
private void onSwitchWallpaperFailLocked(
WallpaperData wallpaper, IRemoteCallback reply, ServiceInfo serviceInfo) {
if (serviceInfo == null) {
- clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, reply);
+ clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, reply);
return;
}
Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked");
@@ -2067,12 +1864,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
WallpaperData data = null;
synchronized (mLock) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- boolean fromForeground = isFromForegroundApp(callingPackage);
- clearWallpaperLocked(which, userId, fromForeground, null);
- } else {
- clearWallpaperLocked(which, userId, null);
- }
+ boolean fromForeground = isFromForegroundApp(callingPackage);
+ clearWallpaperLocked(which, userId, fromForeground, null);
if (which == FLAG_LOCK) {
data = mLockWallpaperMap.get(userId);
@@ -2153,91 +1946,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- // TODO(b/266818039) remove
- private void clearWallpaperLocked(int which, int userId, IRemoteCallback reply) {
-
- if (mIsLockscreenLiveWallpaperEnabled) {
- clearWallpaperLocked(which, userId, false, reply);
- return;
- }
-
- if (which != FLAG_SYSTEM && which != FLAG_LOCK) {
- throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear");
- }
-
- WallpaperData wallpaper = null;
- if (which == FLAG_LOCK) {
- wallpaper = mLockWallpaperMap.get(userId);
- if (wallpaper == null) {
- // It's already gone; we're done.
- if (DEBUG) {
- Slog.i(TAG, "Lock wallpaper already cleared");
- }
- return;
- }
- } else {
- wallpaper = mWallpaperMap.get(userId);
- if (wallpaper == null) {
- // Might need to bring it in the first time to establish our rewrite
- loadSettingsLocked(userId, false, FLAG_SYSTEM);
- wallpaper = mWallpaperMap.get(userId);
- }
- }
- if (wallpaper == null) {
- return;
- }
-
- final long ident = Binder.clearCallingIdentity();
- try {
- if (clearWallpaperBitmaps(wallpaper)) {
- if (which == FLAG_LOCK) {
- mLockWallpaperMap.remove(userId);
- final IWallpaperManagerCallback cb = mKeyguardListener;
- if (cb != null) {
- if (DEBUG) {
- Slog.i(TAG, "Notifying keyguard of lock wallpaper clear");
- }
- try {
- cb.onWallpaperChanged();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify keyguard after wallpaper clear", e);
- }
- }
- saveSettingsLocked(userId);
- return;
- }
- }
-
- RuntimeException e = null;
- try {
- wallpaper.primaryColors = null;
- wallpaper.imageWallpaperPending = false;
- if (userId != mCurrentUserId) return;
- if (bindWallpaperComponentLocked(null, true, false, wallpaper, reply)) {
- return;
- }
- } catch (IllegalArgumentException e1) {
- e = e1;
- }
-
- // This can happen if the default wallpaper component doesn't
- // exist. This should be a system configuration problem, but
- // let's not let it crash the system and just live with no
- // wallpaper.
- Slog.e(TAG, "Default wallpaper component not found!", e);
- clearWallpaperComponentLocked(wallpaper);
- if (reply != null) {
- try {
- reply.sendResult(null);
- } catch (RemoteException e1) {
- Slog.w(TAG, "Failed to notify callback after wallpaper clear", e1);
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
private boolean hasCrossUserPermission() {
final int interactPermission =
mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
@@ -2615,45 +2323,20 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* @param animationDuration Duration of the animation, or 0 when immediate.
*/
public void setInAmbientMode(boolean inAmbientMode, long animationDuration) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- List<IWallpaperEngine> engines = new ArrayList<>();
- synchronized (mLock) {
- mInAmbientMode = inAmbientMode;
- for (WallpaperData data : getActiveWallpapers()) {
- if (data.connection.mInfo == null
- || data.connection.mInfo.supportsAmbientMode()) {
- // TODO(multi-display) Extends this method with specific display.
- IWallpaperEngine engine = data.connection
- .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
- if (engine != null) engines.add(engine);
- }
- }
- }
- for (IWallpaperEngine engine : engines) {
- try {
- engine.setInAmbientMode(inAmbientMode, animationDuration);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to set ambient mode", e);
- }
- }
- return;
- }
-
- final IWallpaperEngine engine;
+ List<IWallpaperEngine> engines = new ArrayList<>();
synchronized (mLock) {
mInAmbientMode = inAmbientMode;
- final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- // The wallpaper info is null for image wallpaper, also use the engine in this case.
- if (data != null && data.connection != null && (data.connection.mInfo == null
- || data.connection.mInfo.supportsAmbientMode())) {
- // TODO(multi-display) Extends this method with specific display.
- engine = data.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
- } else {
- engine = null;
+ for (WallpaperData data : getActiveWallpapers()) {
+ if (data.connection.mInfo == null
+ || data.connection.mInfo.supportsAmbientMode()) {
+ // TODO(multi-display) Extends this method with specific display.
+ IWallpaperEngine engine = data.connection
+ .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
+ if (engine != null) engines.add(engine);
+ }
}
}
-
- if (engine != null) {
+ for (IWallpaperEngine engine : engines) {
try {
engine.setInAmbientMode(inAmbientMode, animationDuration);
} catch (RemoteException e) {
@@ -2664,11 +2347,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private void pauseOrResumeRenderingImmediately(boolean pause) {
synchronized (mLock) {
- final WallpaperData[] wallpapers = mIsLockscreenLiveWallpaperEnabled
- ? getActiveWallpapers() : new WallpaperData[] {
- mWallpaperMap.get(mCurrentUserId) };
- for (WallpaperData data : wallpapers) {
- if (data.connection == null || data.connection.mInfo == null) {
+ for (WallpaperData data : getActiveWallpapers()) {
+ if (data.connection.mInfo == null) {
continue;
}
if (pause || LocalServices.getService(ActivityTaskManagerInternal.class)
@@ -2697,34 +2377,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
checkCallerIsSystemOrSystemUi();
synchronized (mLock) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- for (WallpaperData data : getActiveWallpapers()) {
- data.connection.forEachDisplayConnector(displayConnector -> {
- if (displayConnector.mEngine != null) {
- try {
- displayConnector.mEngine.dispatchWallpaperCommand(
- WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
- }
+ for (WallpaperData data : getActiveWallpapers()) {
+ data.connection.forEachDisplayConnector(displayConnector -> {
+ if (displayConnector.mEngine != null) {
+ try {
+ displayConnector.mEngine.dispatchWallpaperCommand(
+ WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
}
- });
- }
- return;
- }
- final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- if (data != null && data.connection != null) {
- data.connection.forEachDisplayConnector(
- displayConnector -> {
- if (displayConnector.mEngine != null) {
- try {
- displayConnector.mEngine.dispatchWallpaperCommand(
- WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
- }
- }
- });
+ }
+ });
}
}
}
@@ -2735,36 +2398,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
checkCallerIsSystemOrSystemUi();
synchronized (mLock) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- for (WallpaperData data : getActiveWallpapers()) {
- data.connection.forEachDisplayConnector(displayConnector -> {
- if (displayConnector.mEngine != null) {
- try {
- displayConnector.mEngine.dispatchWallpaperCommand(
- WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
- extras);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
- }
+ for (WallpaperData data : getActiveWallpapers()) {
+ data.connection.forEachDisplayConnector(displayConnector -> {
+ if (displayConnector.mEngine != null) {
+ try {
+ displayConnector.mEngine.dispatchWallpaperCommand(
+ WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
+ extras);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
}
- });
- }
- return;
- }
- final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- if (data != null && data.connection != null) {
- data.connection.forEachDisplayConnector(
- displayConnector -> {
- if (displayConnector.mEngine != null) {
- try {
- displayConnector.mEngine.dispatchWallpaperCommand(
- WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
- extras);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
- }
- }
- });
+ }
+ });
}
}
}
@@ -2774,35 +2419,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
*/
private void notifyScreenTurnedOn(int displayId) {
synchronized (mLock) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- for (WallpaperData data : getActiveWallpapers()) {
- if (data.connection.containsDisplay(displayId)) {
- final IWallpaperEngine engine = data.connection
- .getDisplayConnectorOrCreate(displayId).mEngine;
- if (engine != null) {
- try {
- engine.onScreenTurnedOn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify that the screen turned on", e);
- }
+ for (WallpaperData data : getActiveWallpapers()) {
+ if (data.connection.containsDisplay(displayId)) {
+ final IWallpaperEngine engine = data.connection
+ .getDisplayConnectorOrCreate(displayId).mEngine;
+ if (engine != null) {
+ try {
+ engine.onScreenTurnedOn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify that the screen turned on", e);
}
}
}
- return;
- }
- final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- if (data != null
- && data.connection != null
- && data.connection.containsDisplay(displayId)) {
- final IWallpaperEngine engine = data.connection
- .getDisplayConnectorOrCreate(displayId).mEngine;
- if (engine != null) {
- try {
- engine.onScreenTurnedOn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify that the screen turned on", e);
- }
- }
}
}
}
@@ -2812,35 +2440,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
*/
private void notifyScreenTurningOn(int displayId) {
synchronized (mLock) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- for (WallpaperData data : getActiveWallpapers()) {
- if (data.connection.containsDisplay(displayId)) {
- final IWallpaperEngine engine = data.connection
- .getDisplayConnectorOrCreate(displayId).mEngine;
- if (engine != null) {
- try {
- engine.onScreenTurningOn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify that the screen is turning on", e);
- }
+ for (WallpaperData data : getActiveWallpapers()) {
+ if (data.connection.containsDisplay(displayId)) {
+ final IWallpaperEngine engine = data.connection
+ .getDisplayConnectorOrCreate(displayId).mEngine;
+ if (engine != null) {
+ try {
+ engine.onScreenTurningOn();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to notify that the screen is turning on", e);
}
}
}
- return;
- }
- final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- if (data != null
- && data.connection != null
- && data.connection.containsDisplay(displayId)) {
- final IWallpaperEngine engine = data.connection
- .getDisplayConnectorOrCreate(displayId).mEngine;
- if (engine != null) {
- try {
- engine.onScreenTurningOn();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify that the screen is turning on", e);
- }
- }
}
}
}
@@ -2850,25 +2461,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
*/
private void notifyKeyguardGoingAway() {
synchronized (mLock) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- for (WallpaperData data : getActiveWallpapers()) {
- data.connection.forEachDisplayConnector(displayConnector -> {
- if (displayConnector.mEngine != null) {
- try {
- displayConnector.mEngine.dispatchWallpaperCommand(
- WallpaperManager.COMMAND_KEYGUARD_GOING_AWAY,
- -1, -1, -1, new Bundle());
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to notify that the keyguard is going away", e);
- }
- }
- });
- }
- return;
- }
-
- final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
- if (data != null && data.connection != null) {
+ for (WallpaperData data : getActiveWallpapers()) {
data.connection.forEachDisplayConnector(displayConnector -> {
if (displayConnector.mEngine != null) {
try {
@@ -2884,15 +2477,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- @Override
- public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
- checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
- synchronized (mLock) {
- mKeyguardListener = cb;
- }
- return true;
- }
-
private WallpaperData[] getActiveWallpapers() {
WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
@@ -2904,12 +2488,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
: new WallpaperData[0];
}
- // TODO(b/266818039) remove
private WallpaperData[] getWallpapers() {
WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
boolean systemValid = systemWallpaper != null;
- boolean lockValid = lockWallpaper != null && isLockscreenLiveWallpaperEnabled();
+ boolean lockValid = lockWallpaper != null;
return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
: systemValid ? new WallpaperData[]{systemWallpaper}
: lockValid ? new WallpaperData[]{lockWallpaper}
@@ -3043,54 +2626,29 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
lockWallpaper.mWallpaperDimAmount = maxDimAmount;
}
- if (mIsLockscreenLiveWallpaperEnabled) {
- boolean changed = false;
- for (WallpaperData wp : getActiveWallpapers()) {
- if (wp != null && wp.connection != null) {
- wp.connection.forEachDisplayConnector(connector -> {
- if (connector.mEngine != null) {
- try {
- connector.mEngine.applyDimming(maxDimAmount);
- } catch (RemoteException e) {
- Slog.w(TAG, "Can't apply dimming on wallpaper display "
- + "connector", e);
- }
- }
- });
- // Need to extract colors again to re-calculate dark hints after
- // applying dimming.
- wp.mIsColorExtractedFromDim = true;
- pendingColorExtraction.add(wp);
- changed = true;
- }
- }
- if (changed) {
- saveSettingsLocked(wallpaper.userId);
- }
- } else {
- if (wallpaper.connection != null) {
- wallpaper.connection.forEachDisplayConnector(connector -> {
+ boolean changed = false;
+ for (WallpaperData wp : getActiveWallpapers()) {
+ if (wp != null && wp.connection != null) {
+ wp.connection.forEachDisplayConnector(connector -> {
if (connector.mEngine != null) {
try {
connector.mEngine.applyDimming(maxDimAmount);
} catch (RemoteException e) {
- Slog.w(TAG,
- "Can't apply dimming on wallpaper display connector",
- e);
+ Slog.w(TAG, "Can't apply dimming on wallpaper display "
+ + "connector", e);
}
}
});
// Need to extract colors again to re-calculate dark hints after
// applying dimming.
- wallpaper.mIsColorExtractedFromDim = true;
- notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
- if (lockWallpaper != null) {
- lockWallpaper.mIsColorExtractedFromDim = true;
- notifyWallpaperColorsChanged(lockWallpaper, FLAG_LOCK);
- }
- saveSettingsLocked(wallpaper.userId);
+ wp.mIsColorExtractedFromDim = true;
+ pendingColorExtraction.add(wp);
+ changed = true;
}
}
+ if (changed) {
+ saveSettingsLocked(wallpaper.userId);
+ }
}
for (WallpaperData wp: pendingColorExtraction) {
notifyWallpaperColorsChanged(wp, wp.mWhich);
@@ -3246,10 +2804,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) {
Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
+ " updating system wallpaper");
- if (!migrateStaticSystemToLockWallpaperLocked(userId)
- && !isLockscreenLiveWallpaperEnabled()) {
- which |= FLAG_LOCK;
- }
+ migrateStaticSystemToLockWallpaperLocked(userId);
}
wallpaper = getWallpaperSafeLocked(userId, which);
@@ -3277,13 +2832,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private boolean migrateStaticSystemToLockWallpaperLocked(int userId) {
+ private void migrateStaticSystemToLockWallpaperLocked(int userId) {
WallpaperData sysWP = mWallpaperMap.get(userId);
if (sysWP == null) {
if (DEBUG) {
Slog.i(TAG, "No system wallpaper? Not tracking for lock-only");
}
- return true;
+ return;
}
// We know a-priori that there is no lock-only wallpaper currently
@@ -3297,25 +2852,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// Migrate the bitmap files outright; no need to copy
try {
- if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getWallpaperFile().exists()) {
+ if (sysWP.getWallpaperFile().exists()) {
Os.rename(sysWP.getWallpaperFile().getAbsolutePath(),
lockWP.getWallpaperFile().getAbsolutePath());
}
- if (!mIsLockscreenLiveWallpaperEnabled || sysWP.getCropFile().exists()) {
+ if (sysWP.getCropFile().exists()) {
Os.rename(sysWP.getCropFile().getAbsolutePath(),
lockWP.getCropFile().getAbsolutePath());
}
mLockWallpaperMap.put(userId, lockWP);
- if (mIsLockscreenLiveWallpaperEnabled) {
- SELinux.restorecon(lockWP.getWallpaperFile());
- mLastLockWallpaper = lockWP;
- }
- return true;
+ SELinux.restorecon(lockWP.getWallpaperFile());
+ mLastLockWallpaper = lockWP;
} catch (ErrnoException e) {
// can happen when migrating default wallpaper (which is not stored in wallpaperFile)
Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage());
clearWallpaperBitmaps(lockWP);
- return false;
}
}
@@ -3372,13 +2923,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
@VisibleForTesting
boolean setWallpaperComponent(ComponentName name, String callingPackage,
@SetWallpaperFlags int which, int userId) {
- if (mIsLockscreenLiveWallpaperEnabled) {
- boolean fromForeground = isFromForegroundApp(callingPackage);
- return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
- } else {
- setWallpaperComponentInternalLegacy(name, callingPackage, which, userId);
- return true;
- }
+ boolean fromForeground = isFromForegroundApp(callingPackage);
+ return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
}
private boolean setWallpaperComponentInternal(ComponentName name, @SetWallpaperFlags int which,
@@ -3491,87 +3037,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return bindSuccess;
}
- // TODO(b/266818039) Remove this method
- private void setWallpaperComponentInternalLegacy(ComponentName name, String callingPackage,
- @SetWallpaperFlags int which, int userId) {
- userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
- false /* all */, true /* full */, "changing live wallpaper", null /* pkg */);
- checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
-
- int legacyWhich = FLAG_SYSTEM;
- boolean shouldNotifyColors = false;
- WallpaperData wallpaper;
-
- synchronized (mLock) {
- Slog.v(TAG, "setWallpaperComponentLegacy name=" + name + ", which=" + which);
- wallpaper = mWallpaperMap.get(userId);
- if (wallpaper == null) {
- throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
- }
- final long ident = Binder.clearCallingIdentity();
-
- // Live wallpapers can't be specified for keyguard. If we're using a static
- // system+lock image currently, migrate the system wallpaper to be a lock-only
- // image as part of making a different live component active as the system
- // wallpaper.
- if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) {
- if (mLockWallpaperMap.get(userId) == null) {
- // We're using the static imagery and there is no lock-specific image in place,
- // therefore it's a shared system+lock image that we need to migrate.
- Slog.i(TAG, "Migrating current wallpaper to be lock-only before"
- + "updating system wallpaper");
- if (!migrateStaticSystemToLockWallpaperLocked(userId)) {
- which |= FLAG_LOCK;
- }
- }
- }
-
- // New live wallpaper is also a lock wallpaper if nothing is set
- if (mLockWallpaperMap.get(userId) == null) {
- legacyWhich |= FLAG_LOCK;
- }
-
- try {
- wallpaper.imageWallpaperPending = false;
- wallpaper.mWhich = which;
- wallpaper.fromForegroundApp = isFromForegroundApp(callingPackage);
- boolean same = changingToSame(name, wallpaper);
-
- // force rebind when reapplying a system-only wallpaper to system+lock
- boolean forceRebind = same && mLockWallpaperMap.get(userId) != null
- && which == (FLAG_SYSTEM | FLAG_LOCK);
- if (bindWallpaperComponentLocked(name, forceRebind, true, wallpaper, null)) {
- if (!same) {
- wallpaper.primaryColors = null;
- } else {
- if (wallpaper.connection != null) {
- wallpaper.connection.forEachDisplayConnector(displayConnector -> {
- try {
- if (displayConnector.mEngine != null) {
- displayConnector.mEngine.dispatchWallpaperCommand(
- COMMAND_REAPPLY, 0, 0, 0, null);
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Error sending apply message to wallpaper", e);
- }
- });
- }
- }
- wallpaper.wallpaperId = makeWallpaperIdLocked();
- notifyCallbacksLocked(wallpaper);
- shouldNotifyColors = true;
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- if (shouldNotifyColors) {
- notifyWallpaperColorsChanged(wallpaper, legacyWhich);
- notifyWallpaperColorsChanged(mFallbackWallpaper, FLAG_SYSTEM);
- }
- }
-
/**
* Determines if the given component name is the default component. Note: a null name can be
* used to represent the default component.
@@ -3743,21 +3208,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, msg);
return false;
}
- if (mIsLockscreenLiveWallpaperEnabled) {
- maybeDetachLastWallpapers(wallpaper);
- } else if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null
- && !wallpaper.equals(mFallbackWallpaper)) {
- detachWallpaperLocked(mLastWallpaper);
- }
+ maybeDetachLastWallpapers(wallpaper);
wallpaper.wallpaperComponent = componentName;
wallpaper.connection = newConn;
newConn.mReply = reply;
- if (mIsLockscreenLiveWallpaperEnabled) {
- updateCurrentWallpapers(wallpaper);
- } else if (wallpaper.userId == mCurrentUserId && !wallpaper.equals(
- mFallbackWallpaper)) {
- mLastWallpaper = wallpaper;
- }
+ updateCurrentWallpapers(wallpaper);
updateFallbackConnection();
} catch (RemoteException e) {
String msg = "Remote exception for " + componentName + "\n" + e;
@@ -3773,7 +3228,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
// Updates tracking of the currently bound wallpapers.
- // Assumes isLockscreenLiveWallpaperEnabled is true.
private void updateCurrentWallpapers(WallpaperData newWallpaper) {
if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
return;
@@ -3787,8 +3241,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- // Detaches previously bound wallpapers if no longer in use. Assumes
- // isLockscreenLiveWallpaperEnabled is true.
+ // Detaches previously bound wallpapers if no longer in use.
private void maybeDetachLastWallpapers(WallpaperData newWallpaper) {
if (newWallpaper.userId != mCurrentUserId || newWallpaper.equals(mFallbackWallpaper)) {
return;
@@ -3981,11 +3434,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
@Override
- public boolean isLockscreenLiveWallpaperEnabled() {
- return mIsLockscreenLiveWallpaperEnabled;
- }
-
- @Override
public boolean isMultiCropEnabled() {
return mIsMultiCropEnabled;
}
@@ -4074,13 +3522,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private void loadSettingsLocked(int userId, boolean keepDimensionHints, int which) {
initializeFallbackWallpaper();
- WallpaperData wallpaperData = mWallpaperMap.get(userId);
- WallpaperData lockWallpaperData = mLockWallpaperMap.get(userId);
+ boolean restoreFromOld = !mWallpaperMap.contains(userId);
WallpaperDataParser.WallpaperLoadingResult result = mWallpaperDataParser.loadSettingsLocked(
- userId, keepDimensionHints, wallpaperData, lockWallpaperData, which);
+ userId, keepDimensionHints, restoreFromOld, which);
- boolean updateSystem = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_SYSTEM) != 0;
- boolean updateLock = !mIsLockscreenLiveWallpaperEnabled || (which & FLAG_LOCK) != 0;
+ boolean updateSystem = (which & FLAG_SYSTEM) != 0;
+ boolean updateLock = (which & FLAG_LOCK) != 0;
if (updateSystem) mWallpaperMap.put(userId, result.getSystemWallpaperData());
if (updateLock) {
@@ -4243,8 +3690,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mFallbackWallpaper != null) {
dumpWallpaper(mFallbackWallpaper, pw);
}
- pw.print("mIsLockscreenLiveWallpaperEnabled=");
- pw.println(mIsLockscreenLiveWallpaperEnabled);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a01113b26a1e..fd8f6bfd1cc8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3055,7 +3055,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
boolean providesOrientation() {
- return mStyleFillsParent;
+ return mStyleFillsParent || mOccludesParent;
}
@Override
@@ -4030,6 +4030,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mAppStopped) {
abortAndClearOptionsAnimation();
}
+ if (mDisplayContent != null) {
+ mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+ }
}
boolean isFinishing() {
@@ -5388,12 +5391,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLastDeferHidingClient = deferHidingClient;
if (!visible) {
- // If this activity is about to finish/stopped and now becomes invisible, remove it
- // from the unknownApp list in case the activity does not want to draw anything, which
- // keep the user waiting for the next transition to start.
- if (finishing || isState(STOPPED)) {
- displayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
- }
// Because starting window was transferred, this activity may be a trampoline which has
// been occluded by next activity. If it has added windows, set client visibility
// immediately to avoid the client getting RELAYOUT_RES_FIRST_TIME from relayout and
@@ -5837,6 +5834,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
break;
case STOPPED:
mAtmService.updateActivityUsageStats(this, Event.ACTIVITY_STOPPED);
+ if (mDisplayContent != null) {
+ mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
+ }
break;
case DESTROYED:
if (app != null && (mVisible || mVisibleRequested)) {
@@ -6517,7 +6517,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// Reset the last saved PiP snap fraction on app stop.
mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
- mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
if (isClientVisible()) {
// Though this is usually unlikely to happen, still make sure the client is invisible.
setClientVisible(false);
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 01b8bf794dae..52ab9b855c7c 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -539,7 +539,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
if (usf != null) {
mUserSavedFiles.get(userId).remove(code);
mSavedFilesInOrder.remove(usf);
- mPersister.removeSnap(code, userId);
+ mPersister.removeSnapshot(code, userId);
}
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 444470952c5d..c79a8b6b13a1 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -378,8 +378,23 @@ class BLASTSyncEngine {
if (!wc.isSyncFinished(this)) {
allFinished = false;
Slog.i(TAG, "Unfinished container: " + wc);
+ wc.forAllActivities(a -> {
+ if (a.isVisibleRequested()) {
+ if (a.isRelaunching()) {
+ Slog.i(TAG, " " + a + " is relaunching");
+ }
+ a.forAllWindows(w -> {
+ Slog.i(TAG, " " + w + " " + w.mWinAnimator.drawStateToString());
+ }, true /* traverseTopToBottom */);
+ } else if (a.mDisplayContent != null && !a.mDisplayContent
+ .mUnknownAppVisibilityController.allResolved()) {
+ Slog.i(TAG, " UnknownAppVisibility: " + a.mDisplayContent
+ .mUnknownAppVisibilityController.getDebugMessage());
+ }
+ });
}
}
+
for (int i = mDependencies.size() - 1; i >= 0; --i) {
allFinished = false;
Slog.i(TAG, "Unfinished dependency: " + mDependencies.get(i).mSyncId);
diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
index d604402e3e1e..5db02dff8351 100644
--- a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
@@ -58,7 +58,7 @@ class BaseAppSnapshotPersister {
* @param id The id of task that has been removed.
* @param userId The id of the user the task belonged to.
*/
- void removeSnap(int id, int userId) {
+ void removeSnapshot(int id, int userId) {
synchronized (mLock) {
mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue
.createDeleteWriteQueueItem(id, userId, mPersistInfoProvider));
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 06448d0c1e84..022ef6152929 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -28,6 +28,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.media.projection.IMediaProjectionManager;
import android.os.IBinder;
@@ -36,10 +37,12 @@ import android.os.ServiceManager;
import android.view.ContentRecordingSession;
import android.view.ContentRecordingSession.RecordContent;
import android.view.Display;
+import android.view.DisplayInfo;
import android.view.SurfaceControl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.display.feature.DisplayManagerFlags;
/**
* Manages content recording for a particular {@link DisplayContent}.
@@ -47,6 +50,16 @@ import com.android.internal.protolog.common.ProtoLog;
final class ContentRecorder implements WindowContainerListener {
/**
+ * Maximum acceptable anisotropy for the output image.
+ *
+ * Necessary to avoid unnecessary scaling when the anisotropy is almost the same, as it is not
+ * exact anyway. For external displays, we expect an anisoptry of about 2% even if the pixels
+ * are, in fact, square due to the imprecision of the display's actual size (rounded to the
+ * nearest cm).
+ */
+ private static final float MAX_ANISOTROPY = 0.025f;
+
+ /**
* The display content this class is handling recording for.
*/
@NonNull
@@ -87,15 +100,20 @@ final class ContentRecorder implements WindowContainerListener {
@Configuration.Orientation
private int mLastOrientation = ORIENTATION_UNDEFINED;
+ private final boolean mCorrectForAnisotropicPixels;
+
ContentRecorder(@NonNull DisplayContent displayContent) {
- this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
+ this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId),
+ new DisplayManagerFlags().isConnectedDisplayManagementEnabled());
}
@VisibleForTesting
ContentRecorder(@NonNull DisplayContent displayContent,
- @NonNull MediaProjectionManagerWrapper mediaProjectionManager) {
+ @NonNull MediaProjectionManagerWrapper mediaProjectionManager,
+ boolean correctForAnisotropicPixels) {
mDisplayContent = displayContent;
mMediaProjectionManager = mediaProjectionManager;
+ mCorrectForAnisotropicPixels = correctForAnisotropicPixels;
}
/**
@@ -460,6 +478,33 @@ final class ContentRecorder implements WindowContainerListener {
}
}
+ private void computeScaling(int inputSizeX, int inputSizeY,
+ float inputDpiX, float inputDpiY,
+ int outputSizeX, int outputSizeY,
+ float outputDpiX, float outputDpiY,
+ PointF scaleOut) {
+ float relAnisotropy = (inputDpiY / inputDpiX) / (outputDpiY / outputDpiX);
+ if (!mCorrectForAnisotropicPixels
+ || (relAnisotropy > (1 - MAX_ANISOTROPY) && relAnisotropy < (1 + MAX_ANISOTROPY))) {
+ // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
+ // output surface.
+ float scaleX = outputSizeX / (float) inputSizeX;
+ float scaleY = outputSizeY / (float) inputSizeY;
+ float scale = Math.min(scaleX, scaleY);
+ scaleOut.x = scale;
+ scaleOut.y = scale;
+ return;
+ }
+
+ float relDpiX = outputDpiX / inputDpiX;
+ float relDpiY = outputDpiY / inputDpiY;
+
+ float scale = Math.min(outputSizeX / relDpiX / inputSizeX,
+ outputSizeY / relDpiY / inputSizeY);
+ scaleOut.x = scale * relDpiX;
+ scaleOut.y = scale * relDpiY;
+ }
+
/**
* Apply transformations to the mirrored surface to ensure the captured contents are scaled to
* fit and centred in the output surface.
@@ -473,13 +518,19 @@ final class ContentRecorder implements WindowContainerListener {
*/
@VisibleForTesting void updateMirroredSurface(SurfaceControl.Transaction transaction,
Rect recordedContentBounds, Point surfaceSize) {
- // Calculate the scale to apply to the root mirror SurfaceControl to fit the size of the
- // output surface.
- float scaleX = surfaceSize.x / (float) recordedContentBounds.width();
- float scaleY = surfaceSize.y / (float) recordedContentBounds.height();
- float scale = Math.min(scaleX, scaleY);
- int scaledWidth = Math.round(scale * (float) recordedContentBounds.width());
- int scaledHeight = Math.round(scale * (float) recordedContentBounds.height());
+
+ DisplayInfo inputDisplayInfo = mRecordedWindowContainer.mDisplayContent.getDisplayInfo();
+ DisplayInfo outputDisplayInfo = mDisplayContent.getDisplayInfo();
+
+ PointF scale = new PointF();
+ computeScaling(recordedContentBounds.width(), recordedContentBounds.height(),
+ inputDisplayInfo.physicalXDpi, inputDisplayInfo.physicalYDpi,
+ surfaceSize.x, surfaceSize.y,
+ outputDisplayInfo.physicalXDpi, outputDisplayInfo.physicalYDpi,
+ scale);
+
+ int scaledWidth = Math.round(scale.x * (float) recordedContentBounds.width());
+ int scaledHeight = Math.round(scale.y * (float) recordedContentBounds.height());
// Calculate the shift to apply to the root mirror SurfaceControl to centre the mirrored
// contents in the output surface.
@@ -493,10 +544,10 @@ final class ContentRecorder implements WindowContainerListener {
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka "
- + "recorded content size) %d x %d for display %d; display has size %d x "
- + "%d; surface has size %d x %d",
- shiftedX, shiftedY, scale, recordedContentBounds.width(),
+ "Content Recording: Apply transformations of shift %d x %d, scale %f x %f, crop "
+ + "(aka recorded content size) %d x %d for display %d; display has size "
+ + "%d x %d; surface has size %d x %d",
+ shiftedX, shiftedY, scale.x, scale.y, recordedContentBounds.width(),
recordedContentBounds.height(), mDisplayContent.getDisplayId(),
mDisplayContent.getConfiguration().screenWidthDp,
mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y);
@@ -508,7 +559,7 @@ final class ContentRecorder implements WindowContainerListener {
recordedContentBounds.height())
// Scale the root mirror SurfaceControl, based upon the size difference between the
// source (DisplayArea to capture) and output (surface the app reads images from).
- .setMatrix(mRecordedSurface, scale, 0 /* dtdx */, 0 /* dtdy */, scale)
+ .setMatrix(mRecordedSurface, scale.x, 0 /* dtdx */, 0 /* dtdy */, scale.y)
// Position needs to be updated when the mirrored DisplayArea has changed, since
// the content will no longer be centered in the output surface.
.setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index 823fbc9b0f3e..2fabb0ea686a 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -29,7 +29,7 @@ import com.android.window.flags.Flags;
*/
public abstract class Dimmer {
- static final boolean DIMMER_REFACTOR = Flags.dimmerRefactor();
+ static final boolean DIMMER_REFACTOR = Flags.introduceSmootherDimmer();
/**
* The {@link WindowContainer} that our Dims are bounded to. We may be dimming on behalf of the
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b7b5c2af0e3e..4fa6e2990faf 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -937,6 +937,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// so we still request the window to resize if the current frame is empty.
if (!w.getFrame().isEmpty()) {
w.updateLastFrames();
+ mWmService.mFrameChangingWindows.remove(w);
}
w.onResizeHandled();
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index f9fa9e6d4a7b..f6aad4c86220 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -36,7 +36,10 @@ import android.view.WindowManager;
import com.android.server.UiThread;
+import java.util.function.BooleanSupplier;
+import java.util.function.DoubleSupplier;
import java.util.function.IntConsumer;
+import java.util.function.IntSupplier;
import java.util.function.Supplier;
/**
@@ -50,12 +53,12 @@ public class Letterbox {
private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory;
private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
- private final Supplier<Boolean> mAreCornersRounded;
+ private final BooleanSupplier mAreCornersRounded;
private final Supplier<Color> mColorSupplier;
// Parameters for "blurred wallpaper" letterbox background.
- private final Supplier<Boolean> mHasWallpaperBackgroundSupplier;
- private final Supplier<Integer> mBlurRadiusSupplier;
- private final Supplier<Float> mDarkScrimAlphaSupplier;
+ private final BooleanSupplier mHasWallpaperBackgroundSupplier;
+ private final IntSupplier mBlurRadiusSupplier;
+ private final DoubleSupplier mDarkScrimAlphaSupplier;
private final Supplier<SurfaceControl> mParentSurfaceSupplier;
private final Rect mOuter = new Rect();
@@ -82,11 +85,11 @@ public class Letterbox {
*/
public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- Supplier<Boolean> areCornersRounded,
+ BooleanSupplier areCornersRounded,
Supplier<Color> colorSupplier,
- Supplier<Boolean> hasWallpaperBackgroundSupplier,
- Supplier<Integer> blurRadiusSupplier,
- Supplier<Float> darkScrimAlphaSupplier,
+ BooleanSupplier hasWallpaperBackgroundSupplier,
+ IntSupplier blurRadiusSupplier,
+ DoubleSupplier darkScrimAlphaSupplier,
IntConsumer doubleTapCallbackX,
IntConsumer doubleTapCallbackY,
Supplier<SurfaceControl> parentSurface) {
@@ -247,7 +250,7 @@ public class Letterbox {
* Returns {@code true} when using {@link #mFullWindowSurface} instead of {@link mSurfaces}.
*/
private boolean useFullWindowSurface() {
- return mAreCornersRounded.get() || mHasWallpaperBackgroundSupplier.get();
+ return mAreCornersRounded.getAsBoolean() || mHasWallpaperBackgroundSupplier.getAsBoolean();
}
private final class TapEventReceiver extends InputEventReceiver {
@@ -424,7 +427,7 @@ public class Letterbox {
mSurfaceFrameRelative.height());
t.reparent(mSurface, mParentSurface);
- mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.get();
+ mHasWallpaperBackground = mHasWallpaperBackgroundSupplier.getAsBoolean();
updateAlphaAndBlur(t);
t.show(mSurface);
@@ -445,17 +448,17 @@ public class Letterbox {
t.setBackgroundBlurRadius(mSurface, 0);
return;
}
- final float alpha = mDarkScrimAlphaSupplier.get();
+ final float alpha = (float) mDarkScrimAlphaSupplier.getAsDouble();
t.setAlpha(mSurface, alpha);
// Translucent dark scrim can be shown without blur.
- if (mBlurRadiusSupplier.get() <= 0) {
+ if (mBlurRadiusSupplier.getAsInt() <= 0) {
// Removing pre-exesting blur
t.setBackgroundBlurRadius(mSurface, 0);
return;
}
- t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.get());
+ t.setBackgroundBlurRadius(mSurface, mBlurRadiusSupplier.getAsInt());
}
private float[] getRgbColorArray() {
@@ -472,7 +475,7 @@ public class Letterbox {
// and mParentSurface may never be updated in applySurfaceChanges but this
// doesn't mean that update is needed.
|| !mSurfaceFrameRelative.isEmpty()
- && (mHasWallpaperBackgroundSupplier.get() != mHasWallpaperBackground
+ && (mHasWallpaperBackgroundSupplier.getAsBoolean() != mHasWallpaperBackground
|| !mColorSupplier.get().equals(mColor)
|| mParentSurfaceSupplier.get() != mParentSurface);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 7a442e708130..2281395f8cb5 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -847,6 +847,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
handleResizingWindows();
+ clearFrameChangingWindows();
if (mWmService.mDisplayFrozen) {
ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -1015,6 +1016,17 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
/**
+ * Clears frame changing windows after handling moving and resizing windows.
+ */
+ private void clearFrameChangingWindows() {
+ final ArrayList<WindowState> frameChangingWindows = mWmService.mFrameChangingWindows;
+ for (int i = frameChangingWindows.size() - 1; i >= 0; i--) {
+ frameChangingWindows.get(i).updateLastFrames();
+ }
+ frameChangingWindows.clear();
+ }
+
+ /**
* @param w WindowState this method is applied to.
* @param obscured True if there is a window on top of this obscuring the display.
* @param syswin System window?
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 2e7ff7a6b9b8..2be2a1a363db 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -26,6 +26,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import android.os.Trace;
import android.view.WindowManager;
+import android.window.TaskSnapshot;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -85,7 +86,7 @@ class SnapshotController {
if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
if (info.mContainer.isActivityTypeHome()) continue;
final Task task = info.mContainer.asTask();
- if (task != null && !task.isVisibleRequested()) {
+ if (task != null && !task.mCreatedByOrganizer && !task.isVisibleRequested()) {
mTaskSnapshotController.recordSnapshot(task, info);
}
// Won't need to capture activity snapshot in close transition.
@@ -126,6 +127,18 @@ class SnapshotController {
}
mActivitySnapshotController.handleTransitionFinish(windows);
mActivitySnapshotController.endSnapshotProcess();
+ // Remove task snapshot if it is visible at the end of transition.
+ for (int i = changeInfos.size() - 1; i >= 0; --i) {
+ final WindowContainer wc = changeInfos.get(i).mContainer;
+ final Task task = wc.asTask();
+ if (task != null && wc.isVisibleRequested()) {
+ final TaskSnapshot snapshot = mTaskSnapshotController.getSnapshot(task.mTaskId,
+ task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */);
+ if (snapshot != null) {
+ mTaskSnapshotController.removeAndDeleteSnapshot(task.mTaskId, task.mUserId);
+ }
+ }
+ }
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4922e9028ed9..6c31e3e69350 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -199,7 +199,6 @@ import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.AppTimeTracker;
import com.android.server.uri.NeededUriGrants;
-import com.android.window.flags.Flags;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -241,7 +240,6 @@ class Task extends TaskFragment {
private static final String ATTR_ROOT_AFFINITY = "root_affinity";
private static final String ATTR_ROOTHASRESET = "root_has_reset";
private static final String ATTR_AUTOREMOVERECENTS = "auto_remove_recents";
- private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
private static final String ATTR_USERID = "user_id";
private static final String ATTR_USER_SETUP_COMPLETE = "user_setup_complete";
private static final String ATTR_EFFECTIVE_UID = "effective_uid";
@@ -344,7 +342,6 @@ class Task extends TaskFragment {
// the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
boolean autoRemoveRecents; // If true, we should automatically remove the task from
// recents when activity finishes
- boolean askedCompatMode;// Have asked the user about compat mode for this task.
private boolean mHasBeenVisible; // Set if any activities in the task have been visible
String stringName; // caching of toString() result.
@@ -617,7 +614,7 @@ class Task extends TaskFragment {
private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
- boolean _autoRemoveRecents, boolean _askedCompatMode, int _userId, int _effectiveUid,
+ boolean _autoRemoveRecents, int _userId, int _effectiveUid,
String _lastDescription, long lastTimeMoved, boolean neverRelinquishIdentity,
TaskDescription _lastTaskDescription, PersistedTaskSnapshotData _lastSnapshotData,
int taskAffiliation, int prevTaskId, int nextTaskId, int callingUid,
@@ -652,7 +649,6 @@ class Task extends TaskFragment {
rootWasReset = _rootWasReset;
isAvailable = true;
autoRemoveRecents = _autoRemoveRecents;
- askedCompatMode = _askedCompatMode;
mUserSetupComplete = userSetupComplete;
effectiveUid = _effectiveUid;
touchActiveTime();
@@ -1333,7 +1329,7 @@ class Task extends TaskFragment {
clearRootProcess();
- mAtmService.mWindowManager.mTaskSnapshotController.notifyTaskRemovedFromRecents(
+ mAtmService.mWindowManager.mTaskSnapshotController.removeAndDeleteSnapshot(
mTaskId, mUserId);
}
@@ -3304,7 +3300,7 @@ class Task extends TaskFragment {
// Once at the root task level, we want to check {@link #isTranslucent(ActivityRecord)}.
// If true, we want to get the Dimmer from the level above since we don't want to animate
// the dim with the Task.
- if (!isRootTask() || (Flags.dimmerRefactor() && isTranslucentAndVisible())
+ if (!isRootTask() || (Dimmer.DIMMER_REFACTOR && isTranslucentAndVisible())
|| isTranslucent(null)) {
return super.getDimmer();
}
@@ -3768,8 +3764,8 @@ class Task extends TaskFragment {
pw.println(")");
}
pw.print(prefix); pw.print("Activities="); pw.println(mChildren);
- if (!askedCompatMode || !inRecents || !isAvailable) {
- pw.print(prefix); pw.print("askedCompatMode="); pw.print(askedCompatMode);
+ if (!inRecents || !isAvailable) {
+ pw.print(prefix);
pw.print(" inRecents="); pw.print(inRecents);
pw.print(" isAvailable="); pw.println(isAvailable);
}
@@ -3877,7 +3873,6 @@ class Task extends TaskFragment {
}
out.attributeBoolean(null, ATTR_ROOTHASRESET, rootWasReset);
out.attributeBoolean(null, ATTR_AUTOREMOVERECENTS, autoRemoveRecents);
- out.attributeBoolean(null, ATTR_ASKEDCOMPATMODE, askedCompatMode);
out.attributeInt(null, ATTR_USERID, mUserId);
out.attributeBoolean(null, ATTR_USER_SETUP_COMPLETE, mUserSetupComplete);
out.attributeInt(null, ATTR_EFFECTIVE_UID, effectiveUid);
@@ -3975,7 +3970,6 @@ class Task extends TaskFragment {
String windowLayoutAffinity = null;
boolean rootHasReset = false;
boolean autoRemoveRecents = false;
- boolean askedCompatMode = false;
int taskType = 0;
int userId = 0;
boolean userSetupComplete = true;
@@ -4036,9 +4030,6 @@ class Task extends TaskFragment {
case ATTR_AUTOREMOVERECENTS:
autoRemoveRecents = Boolean.parseBoolean(attrValue);
break;
- case ATTR_ASKEDCOMPATMODE:
- askedCompatMode = Boolean.parseBoolean(attrValue);
- break;
case ATTR_USERID:
userId = Integer.parseInt(attrValue);
break;
@@ -4192,7 +4183,6 @@ class Task extends TaskFragment {
.setOrigActivity(origActivity)
.setRootWasReset(rootHasReset)
.setAutoRemoveRecents(autoRemoveRecents)
- .setAskedCompatMode(askedCompatMode)
.setUserId(userId)
.setEffectiveUid(effectiveUid)
.setLastDescription(lastDescription)
@@ -6269,7 +6259,6 @@ class Task extends TaskFragment {
private ComponentName mOrigActivity;
private boolean mRootWasReset;
private boolean mAutoRemoveRecents;
- private boolean mAskedCompatMode;
private int mUserId;
private int mEffectiveUid;
private String mLastDescription;
@@ -6524,11 +6513,6 @@ class Task extends TaskFragment {
return this;
}
- private Builder setAskedCompatMode(boolean askedCompatMode) {
- mAskedCompatMode = askedCompatMode;
- return this;
- }
-
private Builder setAffinityIntent(Intent affinityIntent) {
mAffinityIntent = affinityIntent;
return this;
@@ -6661,7 +6645,7 @@ class Task extends TaskFragment {
Task buildInner() {
return new Task(mAtmService, mTaskId, mIntent, mAffinityIntent, mAffinity,
mRootAffinity, mRealActivity, mOrigActivity, mRootWasReset, mAutoRemoveRecents,
- mAskedCompatMode, mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
+ mUserId, mEffectiveUid, mLastDescription, mLastTimeMoved,
mNeverRelinquishIdentity, mLastTaskDescription, mLastSnapshotData,
mTaskAffiliation, mPrevAffiliateTaskId, mNextAffiliateTaskId, mCallingUid,
mCallingPackage, mCallingFeatureId, mResizeMode, mSupportsPictureInPicture,
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 906b3b55e015..82d34246f857 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -103,7 +103,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.am.HostingRecord;
import com.android.server.pm.pkg.AndroidPackage;
-import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -210,7 +209,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
*/
int mMinHeight;
- Dimmer mDimmer = Flags.dimmerRefactor()
+ Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
? new SmoothDimmer(this) : new LegacyDimmer(this);
/** Apply the dim layer on the embedded TaskFragment. */
@@ -391,41 +390,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
new EnsureActivitiesVisibleHelper(this);
- private final EnsureVisibleActivitiesConfigHelper mEnsureVisibleActivitiesConfigHelper =
- new EnsureVisibleActivitiesConfigHelper();
- private class EnsureVisibleActivitiesConfigHelper implements Predicate<ActivityRecord> {
- private boolean mUpdateConfig;
- private boolean mPreserveWindow;
- private boolean mBehindFullscreen;
-
- void reset(boolean preserveWindow) {
- mPreserveWindow = preserveWindow;
- mUpdateConfig = false;
- mBehindFullscreen = false;
- }
-
- void process(ActivityRecord start, boolean preserveWindow) {
- if (start == null || !start.isVisibleRequested()) {
- return;
- }
- reset(preserveWindow);
- forAllActivities(this, start, true /* includeBoundary */,
- true /* traverseTopToBottom */);
-
- if (mUpdateConfig) {
- // Ensure the resumed state of the focus activity if we updated the configuration of
- // any activity.
- mRootWindowContainer.resumeFocusedTasksTopActivities();
- }
- }
-
- @Override
- public boolean test(ActivityRecord r) {
- mUpdateConfig |= r.ensureActivityConfiguration(0 /*globalChanges*/, mPreserveWindow);
- mBehindFullscreen |= r.occludesParent();
- return mBehindFullscreen;
- }
- }
/** Creates an embedded task fragment. */
TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken,
@@ -2163,13 +2127,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return getTask() != null ? getTask().mTaskId : INVALID_TASK_ID;
}
- /**
- * Ensures all visible activities at or below the input activity have the right configuration.
- */
- void ensureVisibleActivitiesConfiguration(ActivityRecord start, boolean preserveWindow) {
- mEnsureVisibleActivitiesConfigHelper.process(start, preserveWindow);
- }
-
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig) {
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 2b12e7497233..d8e18e47fa89 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -284,9 +284,9 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
}
}
- void notifyTaskRemovedFromRecents(int taskId, int userId) {
+ void removeAndDeleteSnapshot(int taskId, int userId) {
mCache.onIdRemoved(taskId);
- mPersister.onTaskRemovedFromRecents(taskId, userId);
+ mPersister.removeSnapshot(taskId, userId);
}
void removeSnapshotCache(int taskId) {
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 3e8c0177b3b3..233daadfc496 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -67,10 +67,11 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
* @param taskId The id of task that has been removed.
* @param userId The id of the user the task belonged to.
*/
- void onTaskRemovedFromRecents(int taskId, int userId) {
+ @Override
+ void removeSnapshot(int taskId, int userId) {
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
- super.removeSnap(taskId, userId);
+ super.removeSnapshot(taskId, userId);
}
}
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index 41c1e793dd90..c0713966d8de 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -70,6 +70,9 @@ class UnknownAppVisibilityController {
}
boolean isVisibilityUnknown(ActivityRecord r) {
+ if (mUnknownApps.isEmpty()) {
+ return false;
+ }
return mUnknownApps.containsKey(r);
}
@@ -90,6 +93,9 @@ class UnknownAppVisibilityController {
}
void appRemovedOrHidden(@NonNull ActivityRecord activity) {
+ if (mUnknownApps.isEmpty()) {
+ return;
+ }
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App removed or hidden activity=" + activity);
}
@@ -117,8 +123,11 @@ class UnknownAppVisibilityController {
* Notifies that {@param activity} has finished resuming.
*/
void notifyAppResumedFinished(@NonNull ActivityRecord activity) {
- if (mUnknownApps.containsKey(activity)
- && mUnknownApps.get(activity) == UNKNOWN_STATE_WAITING_RESUME) {
+ if (mUnknownApps.isEmpty()) {
+ return;
+ }
+ final Integer state = mUnknownApps.get(activity);
+ if (state != null && state == UNKNOWN_STATE_WAITING_RESUME) {
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App resume finished activity=" + activity);
}
@@ -130,13 +139,16 @@ class UnknownAppVisibilityController {
* Notifies that {@param activity} has relaid out.
*/
void notifyRelayouted(@NonNull ActivityRecord activity) {
- if (!mUnknownApps.containsKey(activity)) {
+ if (mUnknownApps.isEmpty()) {
+ return;
+ }
+ final Integer state = mUnknownApps.get(activity);
+ if (state == null) {
return;
}
if (DEBUG_UNKNOWN_APP_VISIBILITY) {
Slog.d(TAG, "App relayouted appWindow=" + activity);
}
- int state = mUnknownApps.get(activity);
if (state == UNKNOWN_STATE_WAITING_RELAYOUT || activity.mStartingWindow != null) {
mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
mDisplayContent.notifyKeyguardFlagsChanged();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 94e66ffd8373..33ef3c5629e3 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -45,7 +45,6 @@ import android.os.Debug;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.util.ArraySet;
import android.util.MathUtils;
import android.util.Slog;
@@ -117,8 +116,6 @@ class WallpaperController {
private boolean mShouldOffsetWallpaperCenter;
- final boolean mIsLockscreenLiveWallpaperEnabled;
-
private final Consumer<WindowState> mFindWallpapers = w -> {
if (w.mAttrs.type == TYPE_WALLPAPER) {
WallpaperWindowToken token = w.mToken.asWallpaperToken();
@@ -236,9 +233,6 @@ class WallpaperController {
WallpaperController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
mDisplayContent = displayContent;
- mIsLockscreenLiveWallpaperEnabled =
- SystemProperties.getBoolean("persist.wm.debug.lockscreen_live_wallpaper", true);
-
Resources resources = service.mContext.getResources();
mMinWallpaperScale =
resources.getFloat(com.android.internal.R.dimen.config_wallpaperMinScale);
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 1ed14310a500..15bd6078dc2d 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -82,18 +82,16 @@ class WallpaperWindowToken extends WindowToken {
return;
}
mShowWhenLocked = showWhenLocked;
- if (mDisplayContent.mWallpaperController.mIsLockscreenLiveWallpaperEnabled) {
- // Move the window token to the front (private) or back (showWhenLocked). This is
- // possible
- // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
- // windows.
- final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
-
- // Note: Moving all the way to the front or back breaks ordering based on addition
- // times.
- // We should never have more than one non-animating token of each type.
- getParent().positionChildAt(position, this /* child */, false /*includingParents */);
- }
+ // Move the window token to the front (private) or back (showWhenLocked). This is
+ // possible
+ // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
+ // windows.
+ final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
+
+ // Note: Moving all the way to the front or back breaks ordering based on addition
+ // times.
+ // We should never have more than one non-animating token of each type.
+ getParent().positionChildAt(position, this /* child */, false /*includingParents */);
}
boolean canShowWhenLocked() {
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index fbd226e46edf..1456184beb1e 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -51,7 +51,8 @@ public class WindowFrames {
final Rect mFrame = new Rect();
/**
- * The last real frame that was reported to the client.
+ * The frame used to check if mFrame is changed, e.g., moved or resized. It will be committed
+ * after handling the moving or resizing windows.
*/
final Rect mLastFrame = new Rect();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 88f72f9dbc90..4a074ff25c74 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -588,6 +588,12 @@ public class WindowManagerService extends IWindowManager.Stub
final ArrayList<WindowState> mResizingWindows = new ArrayList<>();
/**
+ * Windows that their frames are being changed. Used so we can clear the frame-changing states
+ * after handling the moved or resized windows.
+ */
+ final ArrayList<WindowState> mFrameChangingWindows = new ArrayList<>();
+
+ /**
* Mapping of displayId to {@link DisplayImePolicy}.
* Note that this can be accessed without holding the lock.
*/
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3a793e921c68..f14a6f912af1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1200,6 +1200,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
void updateTrustedOverlay() {
mInputWindowHandle.setTrustedOverlay(getPendingTransaction(), mSurfaceControl,
isWindowTrustedOverlay());
+ mInputWindowHandle.forceChange();
}
boolean isWindowTrustedOverlay() {
@@ -1346,6 +1347,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
windowFrames.setContentChanged(true);
}
+ if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)
+ || !windowFrames.mRelFrame.equals(windowFrames.mLastRelFrame)) {
+ mWmService.mFrameChangingWindows.add(this);
+ }
+
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
if (!windowFrames.mFrame.equals(windowFrames.mLastFrame)) {
mMovedByResize = true;
@@ -3716,10 +3722,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mDragResizingChangeReported = true;
mWindowFrames.clearReportResizeHints();
- // We update mLastFrame always rather than in the conditional with the last inset
- // variables, because mFrameSizeChanged only tracks the width and height changing.
- updateLastFrames();
-
final int prevRotation = mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation();
fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index debd891400b6..215934f4dc09 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -272,12 +272,12 @@
<xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
<xs:annotation name="final"/>
</xs:element>
- <!-- Animation time for brightness increase in millis -->
- <xs:element name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+ <!-- Animation speed for brightness increase. In framework brightness units per second. -->
+ <xs:element name="screenBrightnessRampIncrease" type="nonNegativeDecimal">
<xs:annotation name="final"/>
</xs:element>
- <!-- Animation time for brightness decrease in millis -->
- <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+ <!-- Animation speed for brightness decrease. In framework brightness units per second. -->
+ <xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
<xs:annotation name="final"/>
</xs:element>
</xs:complexType>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 2d27f0c79660..f7e004375071 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -179,15 +179,15 @@ package com.android.server.display.config {
public class HdrBrightnessConfig {
ctor public HdrBrightnessConfig();
method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
- method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
- method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+ method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
+ method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
- method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
- method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
+ method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
}
public class HighBrightnessMode {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 25e8475fcf42..323d387eff1a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -24,6 +24,7 @@ import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_HARDWARE_LIMIT
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_STORAGE_LIMIT_REACHED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_CLEARED;
import static android.app.admin.PolicyUpdateResult.RESULT_POLICY_SET;
+import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
import android.Manifest;
@@ -65,7 +66,6 @@ import android.util.Xml;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
@@ -159,7 +159,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
policyDefinition, userId)) {
return;
@@ -282,7 +282,7 @@ final class DevicePolicyEngine {
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -428,7 +428,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
policyDefinition, UserHandle.USER_ALL)) {
return;
@@ -499,7 +499,7 @@ final class DevicePolicyEngine {
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1781,7 +1781,7 @@ final class DevicePolicyEngine {
private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (FlagUtils.isDevicePolicySizeTrackingEnabled()) {
+ if (devicePolicySizeTrackingEnabled()) {
if (mAdminPolicySize != null) {
for (int i = 0; i < mAdminPolicySize.size(); i++) {
int userId = mAdminPolicySize.keyAt(i);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 5a620a3b87f5..5f2d87cf7e80 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -219,6 +219,7 @@ import static android.app.admin.ProvisioningException.ERROR_REMOVE_NON_REQUIRED_
import static android.app.admin.ProvisioningException.ERROR_SETTING_PROFILE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_SET_DEVICE_OWNER_FAILED;
import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
+import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -490,7 +491,6 @@ import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
-import com.android.server.devicepolicy.flags.FlagUtils;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pdb.PersistentDataBlockManagerInternal;
@@ -3430,7 +3430,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
revertTransferOwnershipIfNecessaryLocked();
- if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (!policyEngineMigrationV2Enabled()) {
updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
}
}
@@ -21571,7 +21571,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Objects.requireNonNull(packageName, "Admin package name must be provided");
final CallerIdentity caller = getCallerIdentity(packageName);
- if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (!policyEngineMigrationV2Enabled()) {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
"USB data signaling can only be controlled by a device owner or "
@@ -21581,7 +21581,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
synchronized (getLockObject()) {
- if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (policyEngineMigrationV2Enabled()) {
EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
/* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
caller.getPackageName(),
@@ -21621,7 +21621,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean isUsbDataSignalingEnabled(String packageName) {
final CallerIdentity caller = getCallerIdentity(packageName);
- if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+ if (policyEngineMigrationV2Enabled()) {
Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
PolicyDefinition.USB_DATA_SIGNALING,
caller.getUserId());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
deleted file mode 100644
index 1a45782ccd39..000000000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
+++ /dev/null
@@ -1,16 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-aconfig_declarations {
- name: "device_policy_aconfig_flags",
- package: "com.android.server.devicepolicy.flags",
- srcs: [
- "flags.aconfig",
- ],
-}
-
-java_aconfig_library {
- name: "device_policy_aconfig_flags_lib",
- aconfig_declarations: "device_policy_aconfig_flags",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
deleted file mode 100644
index 7e17ef111cf0..000000000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.devicepolicy.flags;
-
-import static com.android.server.devicepolicy.flags.Flags.devicePolicySizeTrackingEnabled;
-import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
-
-import android.os.Binder;
-
-public final class FlagUtils {
- private FlagUtils(){}
-
- public static boolean isPolicyEngineMigrationV2Enabled() {
- return Binder.withCleanCallingIdentity(() -> {
- return policyEngineMigrationV2Enabled();
- });
- }
-
- public static boolean isDevicePolicySizeTrackingEnabled() {
- return Binder.withCleanCallingIdentity(() -> {
- return devicePolicySizeTrackingEnabled();
- });
- }
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
deleted file mode 100644
index 0dde496e7285..000000000000
--- a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
+++ /dev/null
@@ -1,14 +0,0 @@
-package: "com.android.server.devicepolicy.flags"
-
-flag {
- name: "policy_engine_migration_v2_enabled"
- namespace: "enterprise"
- description: "V2 of the policy engine migrations for Android V"
- bug: "289520697"
-}
-flag {
- name: "device_policy_size_tracking_enabled"
- namespace: "enterprise"
- description: "Add feature to track the total policy size and have a max threshold."
- bug: "281543351"
-} \ No newline at end of file
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 924e2f8ec654..0d024d6a4c58 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2083,17 +2083,14 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
- // Devices without WebView/JavaScript cannot support PAC proxies.
- if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
- t.traceBegin("StartPacProxyService");
- try {
- pacProxyService = new PacProxyService(context);
- ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
- } catch (Throwable e) {
- reportWtf("starting PacProxyService", e);
- }
- t.traceEnd();
+ t.traceBegin("StartPacProxyService");
+ try {
+ pacProxyService = new PacProxyService(context);
+ ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
+ } catch (Throwable e) {
+ reportWtf("starting PacProxyService", e);
}
+ t.traceEnd();
t.traceBegin("StartConnectivityService");
// This has to be called after NetworkManagementService, NetworkStatsService
diff --git a/services/midi/Android.bp b/services/midi/Android.bp
index 4b5f8a7bf0ac..a385fe38495b 100644
--- a/services/midi/Android.bp
+++ b/services/midi/Android.bp
@@ -18,8 +18,8 @@ java_library_static {
name: "services.midi",
defaults: ["platform_service_defaults"],
srcs: [":services.midi-sources"],
- libs: ["services.core"],
- static_libs: [
+ libs: [
+ "services.core",
"aconfig_midi_flags_java_lib",
],
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
index 6ff7b2601b79..b63a58a96b8c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -37,6 +37,7 @@ import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -598,6 +599,28 @@ public class InputMethodServiceTest {
false /* orientationPortrait */);
}
+ @Test
+ public void switchesKeyboardLayout_withShortcut_onlyIfImeVisible() throws Exception {
+ setShowImeWithHardKeyboard(true /* enabled */);
+
+ assertThat(mInputMethodService.isInputViewShown()).isFalse();
+ assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+ new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+ KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isFalse();
+
+ verifyInputViewStatusOnMainSync(
+ () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
+ true /* expected */,
+ true /* inputViewStarted */);
+
+ assertThat(mInputMethodService.isInputViewShown()).isTrue();
+ assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
+ new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
+ KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isTrue();
+ }
+
/**
* This checks that when the system navigation bar is not created (e.g. emulator),
* then the IME caption bar is also not created.
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index f3ac7d55c5db..12cd0f6e1a7c 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -143,8 +143,8 @@ class PackageManagerComponentLabelIconOverrideTest {
val result: Result,
val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME)
) {
- constructor(pkgName: String, appType: AppType, exception: Class<out Exception>)
- : this(pkgName, appType, Result.Exception(exception))
+ constructor(pkgName: String, appType: AppType, exception: Class<out Exception>) :
+ this(pkgName, appType, Result.Exception(exception))
val expectedLabel = when (result) {
Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL
@@ -299,11 +299,9 @@ class PackageManagerComponentLabelIconOverrideTest {
.hideAsFinal()
private fun makePkgSetting(pkgName: String, pkg: AndroidPackageInternal) =
- PackageSetting(
- pkgName, null, File("/test"),
- null, null, null, null, 0, 0, 0, 0, null, null, null, null, null,
- UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c")
- ).apply {
+ PackageSetting(pkgName, null, File("/test"), 0, 0,
+ UUID.fromString("3f9d52b7-d7b4-406a-a1da-d9f19984c72c"))
+ .apply {
if (params.isSystem) {
this.flags = this.flags or ApplicationInfo.FLAG_SYSTEM
}
@@ -373,7 +371,7 @@ class PackageManagerComponentLabelIconOverrideTest {
whenever(this.isCallerRecents(anyInt())) { false }
}
val mockAppsFilter: AppsFilterImpl = mockThrowOnUnmocked {
- whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
+ whenever(this.shouldFilterApplication(any<PackageDataSnapshot>(), anyInt(),
any<PackageSetting>(), any<PackageSetting>(), anyInt())) { false }
whenever(this.snapshot()) { this@mockThrowOnUnmocked }
whenever(registerObserver(any())).thenCallRealMethod()
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index 6eacef767042..c617ec49ab32 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -58,6 +58,7 @@ java_test_host {
":PackageManagerTestOverlayTarget",
":PackageManagerTestOverlayTargetNoOverlayable",
":PackageManagerTestAppDeclaresStaticLibrary",
+ ":PackageManagerTestAppDifferentPkgName",
":PackageManagerTestAppStub",
":PackageManagerTestAppUsesStaticLibrary",
":PackageManagerTestAppVersion1",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
index c4906041ea5d..304f605d5b95 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/TamperedUpdatedSystemPackageTest.kt
@@ -44,6 +44,10 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
private const val VERSION_TWO_ALT_KEY = "PackageManagerTestAppVersion2AltKey.apk"
private const val VERSION_TWO_ALT_KEY_IDSIG =
"PackageManagerTestAppVersion2AltKey.apk.idsig"
+
+ private const val ANOTHER_PKG_NAME = "com.android.server.pm.test.test_app2"
+ private const val ANOTHER_PKG = "PackageManagerTestAppDifferentPkgName.apk"
+
private const val STRICT_SIGNATURE_CONFIG_PATH =
"/system/etc/sysconfig/preinstalled-packages-strict-signature.xml"
private const val TIMESTAMP_REFERENCE_FILE_PATH = "/data/local/tmp/timestamp.ref"
@@ -74,6 +78,7 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
@After
fun removeApk() {
device.uninstallPackage(TEST_PKG_NAME)
+ device.uninstallPackage(ANOTHER_PKG_NAME)
}
@Before
@@ -90,7 +95,9 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
.readText()
.replace(
"</config>",
- "<require-strict-signature package=\"${TEST_PKG_NAME}\"/></config>"
+ "<require-strict-signature package=\"${TEST_PKG_NAME}\"/>" +
+ "<require-strict-signature package=\"${ANOTHER_PKG_NAME}\"/>" +
+ "</config>"
)
writeText(newConfigText)
}
@@ -146,10 +153,7 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
tempFolder.newFile()
)
assertThat(device.installPackage(versionTwoFile, true)).isNull()
- val baseApkPath = device.executeShellCommand("pm path ${TEST_PKG_NAME}")
- .lineSequence()
- .first()
- .replace("package:", "")
+ val baseApkPath = getBaseApkPath(TEST_PKG_NAME)
assertThat(baseApkPath).doesNotContain(productPath.toString())
preparer.pushResourceFile(VERSION_TWO_ALT_KEY_IDSIG, baseApkPath.toString() + ".idsig")
@@ -175,4 +179,23 @@ class TamperedUpdatedSystemPackageTest : BaseHostJUnit4Test() {
assertThat(device.executeShellCommand("pm path ${TEST_PKG_NAME}"))
.contains(productPath.toString())
}
+
+ @Test
+ fun allowlistedPackageIsNotASystemApp() {
+ // If an allowlisted package isn't a system app, make sure install and boot still works
+ // normally.
+ assertThat(device.installJavaResourceApk(tempFolder, ANOTHER_PKG, /* reinstall */ false))
+ .isNull()
+ assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+
+ preparer.reboot()
+ assertThat(getBaseApkPath(ANOTHER_PKG_NAME)).startsWith("/data/app/")
+ }
+
+ private fun getBaseApkPath(pkgName: String): String {
+ return device.executeShellCommand("pm path $pkgName")
+ .lineSequence()
+ .first()
+ .replace("package:", "")
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index bee7c4019fc1..b826590b7440 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -76,3 +76,11 @@ android_test_helper_app {
certificate: ":FrameworksServicesTests_keyset_A_cert",
v4_signature: true,
}
+
+android_test_helper_app {
+ name: "PackageManagerTestAppDifferentPkgName",
+ manifest: "AndroidManifestDifferentPkgName.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
new file mode 100644
index 000000000000..0c5d36e38972
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/AndroidManifestDifferentPkgName.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test.test_app2"
+ android:versionCode="1"
+ >
+
+ <permission
+ android:name="com.android.server.pm.test.test_app.TEST_PERMISSION"
+ android:protectionLevel="normal"
+ />
+
+ <application>
+ <activity android:name="com.android.server.pm.test.test_app.TestActivity"
+ android:label="PackageManagerTestApp" />
+ </application>
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index cbedcaf97358..2810145c08a9 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -967,20 +967,12 @@ public class PackageManagerSettingsTests {
PACKAGE_NAME,
REAL_PACKAGE_NAME,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE);
origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
final PackageSetting testPkgSetting01 = new PackageSetting(origPkgSetting01);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
@@ -989,23 +981,15 @@ public class PackageManagerSettingsTests {
@Test
public void testPackageStateCopy02() {
final PackageSetting origPkgSetting01 = new PackageSetting(
- PACKAGE_NAME /*pkgName*/,
- REAL_PACKAGE_NAME /*realPkgName*/,
+ PACKAGE_NAME,
+ REAL_PACKAGE_NAME,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_HAS_CODE,
ApplicationInfo.PRIVATE_FLAG_PRIVILEGED|ApplicationInfo.PRIVATE_FLAG_HIDDEN,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE);
origPkgSetting01.setUserState(0, 100, 100, 1, true, false, false, false, 0, null, false,
false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}),
new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning",
@@ -1028,20 +1012,10 @@ public class PackageManagerSettingsTests {
PACKAGE_NAME /*pkgName*/,
REAL_PACKAGE_NAME /*realPkgName*/,
UPDATED_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- null /*primaryCpuAbiString*/,
- null /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- UPDATED_VERSION_CODE,
0 /*pkgFlags*/,
0 /*pkgPrivateFlags*/,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setLongVersionCode(UPDATED_VERSION_CODE);
testPkgSetting01.copyPackageSetting(origPkgSetting01, true);
verifySettingCopy(origPkgSetting01, testPkgSetting01);
verifyUserStatesCopy(origPkgSetting01.readUserState(0),
@@ -1077,7 +1051,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
@@ -1118,7 +1091,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPrimaryCpuAbi(), is("arm64-v8a"));
@@ -1161,7 +1133,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
fail("Expected a PackageManagerException");
@@ -1200,7 +1171,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getPath(), is(UPDATED_CODE_PATH));
@@ -1248,7 +1218,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
@@ -1296,7 +1265,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
@@ -1345,7 +1313,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(10064));
@@ -1391,7 +1358,6 @@ public class PackageManagerSettingsTests {
null /*usesStaticLibrariesVersions*/,
null /*mimeGroups*/,
UUID.randomUUID(),
- false /*isPersistent*/,
34 /*targetSdkVersion*/,
null /*restrictUpdateHash*/);
assertThat(testPkgSetting01.getAppId(), is(0));
@@ -1717,20 +1683,13 @@ public class PackageManagerSettingsTests {
PACKAGE_NAME,
REAL_PACKAGE_NAME,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
pkgFlags,
0 /*privateFlags*/,
- sharedUserId,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE)
+ .setSharedUserAppId(sharedUserId);
}
private PackageSetting createPackageSetting(String packageName) {
@@ -1738,20 +1697,12 @@ public class PackageManagerSettingsTests {
packageName,
packageName,
INITIAL_CODE_PATH /*codePath*/,
- null /*legacyNativeLibraryPathString*/,
- "x86_64" /*primaryCpuAbiString*/,
- "x86" /*secondaryCpuAbiString*/,
- null /*cpuAbiOverrideString*/,
- INITIAL_VERSION_CODE,
0,
0 /*privateFlags*/,
- 0,
- null /*usesSdkLibraries*/,
- null /*usesSdkLibrariesVersions*/,
- null /*usesStaticLibraries*/,
- null /*usesStaticLibrariesVersions*/,
- null /*mimeGroups*/,
- UUID.randomUUID());
+ UUID.randomUUID())
+ .setPrimaryCpuAbi("x86_64")
+ .setSecondaryCpuAbi("x86")
+ .setLongVersionCode(INITIAL_VERSION_CODE);
}
static @NonNull List<UserInfo> createFakeUsers() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index c12aedb72350..a400f1243afb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -90,7 +90,9 @@ public class DisplayBrightnessStateTest {
.append("\n isSlowChange:")
.append(displayBrightnessState.isSlowChange())
.append("\n maxBrightness:")
- .append(displayBrightnessState.getMaxBrightness());
+ .append(displayBrightnessState.getMaxBrightness())
+ .append("\n customAnimationRate:")
+ .append(displayBrightnessState.getCustomAnimationRate());
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index c37d21ae1cc0..179a9d5f748e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -570,9 +570,9 @@ public final class DisplayDeviceConfigTest {
assertNotNull(data);
assertEquals(2, data.mMaxBrightnessLimits.size());
assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
- assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+ assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
- assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+ assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
@@ -841,9 +841,9 @@ public final class DisplayDeviceConfigTest {
+ " </point>\n"
+ " </brightnessMap>\n"
+ " <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
- + " <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+ + " <screenBrightnessRampIncrease>0.11</screenBrightnessRampIncrease>\n"
+ " <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
- + " <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+ + " <screenBrightnessRampDecrease>0.1</screenBrightnessRampDecrease>\n"
+ "</hdrBrightnessConfig>";
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 8b54d6d22c2b..47521d13e49c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -74,6 +74,7 @@ import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.feature.DisplayManagerFlags;
@@ -1314,6 +1315,54 @@ public final class DisplayPowerController2Test {
}
@Test
+ public void testRampRateForClampersControllerApplied() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
+ }
+
+ @Test
+ public void testRampRateForClampersControllerNotApplied_ifDoze() {
+ float transitionRate = 1.5f;
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ dpr.dozeScreenState = Display.STATE_UNKNOWN;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(transitionRate).build());
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(anyFloat(), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_DECREASE), anyBoolean());
+ verify(mHolder.animator, never()).animateTo(anyFloat(), anyFloat(),
+ eq(transitionRate), anyBoolean());
+ }
+
+ @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_ADAPTIVE_TONE_IMPROVEMENTS_1)
public void testRampMaxTimeInteractiveThenIdle() {
// Send a display power request
@@ -1637,13 +1686,20 @@ public final class DisplayPowerController2Test {
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
final HdrClamper hdrClamper = mock(HdrClamper.class);
+ BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(clamperController.clamp(any(), anyFloat(), anyBoolean())).thenAnswer(
+ invocation -> DisplayBrightnessState.builder()
+ .setIsSlowChange(invocation.getArgument(2))
+ .setBrightness(invocation.getArgument(1))
+ .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setCustomAnimationRate(-1).build());
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
- flags));
+ clamperController, flags));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -1662,8 +1718,8 @@ public final class DisplayPowerController2Test {
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, wakelockController,
- screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
- brightnessMappingStrategy, injector, config);
+ screenOffBrightnessSensorController, hbmController, hdrClamper, clamperController,
+ hbmMetadata, brightnessMappingStrategy, injector, config);
}
/**
@@ -1682,6 +1738,7 @@ public final class DisplayPowerController2Test {
public final HighBrightnessModeController hbmController;
public final HdrClamper hdrClamper;
+ public final BrightnessClamperController clamperController;
public final HighBrightnessModeMetadata hbmMetadata;
public final BrightnessMappingStrategy brightnessMappingStrategy;
public final DisplayPowerController2.Injector injector;
@@ -1695,6 +1752,7 @@ public final class DisplayPowerController2Test {
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController hbmController,
HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
HighBrightnessModeMetadata hbmMetadata,
BrightnessMappingStrategy brightnessMappingStrategy,
DisplayPowerController2.Injector injector,
@@ -1709,6 +1767,7 @@ public final class DisplayPowerController2Test {
this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
this.hbmController = hbmController;
this.hdrClamper = hdrClamper;
+ this.clamperController = clamperController;
this.hbmMetadata = hbmMetadata;
this.brightnessMappingStrategy = brightnessMappingStrategy;
this.injector = injector;
@@ -1728,6 +1787,8 @@ public final class DisplayPowerController2Test {
private final HdrClamper mHdrClamper;
+ private final BrightnessClamperController mClamperController;
+
private final DisplayManagerFlags mFlags;
TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
@@ -1738,6 +1799,7 @@ public final class DisplayPowerController2Test {
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController highBrightnessModeController,
HdrClamper hdrClamper,
+ BrightnessClamperController clamperController,
DisplayManagerFlags flags) {
mDisplayPowerState = dps;
mAnimator = animator;
@@ -1748,6 +1810,7 @@ public final class DisplayPowerController2Test {
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
mHdrClamper = hdrClamper;
+ mClamperController = clamperController;
mFlags = flags;
}
@@ -1864,6 +1927,14 @@ public final class DisplayPowerController2Test {
}
@Override
+ BrightnessClamperController getBrightnessClamperController(Handler handler,
+ BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ BrightnessClamperController.DisplayDeviceData data, Context context,
+ DisplayManagerFlags flags) {
+ return mClamperController;
+ }
+
+ @Override
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return mDisplayWhiteBalanceControllerMock;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 32e28715cc74..a77a9586fb43 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -429,7 +429,7 @@ public class LocalDisplayAdapterTest {
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -451,7 +451,7 @@ public class LocalDisplayAdapterTest {
SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 120f);
mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
new Display.Mode(displayMode2.width, displayMode2.height,
- displayMode2.refreshRate));
+ displayMode2.peakRefreshRate));
updateAvailableDisplays();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
@@ -485,7 +485,7 @@ public class LocalDisplayAdapterTest {
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.peakRefreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -947,7 +947,7 @@ public class LocalDisplayAdapterTest {
// Set the user preferred display mode
mListener.addedDisplays.get(0).setUserPreferredDisplayModeLocked(
new Display.Mode(
- displayMode3.width, displayMode3.height, displayMode3.refreshRate));
+ displayMode3.width, displayMode3.height, displayMode3.peakRefreshRate));
updateAvailableDisplays();
waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
displayDeviceInfo = mListener.addedDisplays.get(
@@ -992,6 +992,52 @@ public class LocalDisplayAdapterTest {
}
@Test
+ public void testGetAndSetDisplayModesDisambiguatesByVsyncRate() throws Exception {
+ SurfaceControl.DisplayMode displayMode1 = createFakeDisplayMode(0, 1920, 1080, 60f, 120f);
+ SurfaceControl.DisplayMode displayMode2 = createFakeDisplayMode(1, 1920, 1080, 60f, 60f);
+ SurfaceControl.DisplayMode[] modes =
+ new SurfaceControl.DisplayMode[]{displayMode1, displayMode2};
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, 0);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ DisplayDeviceInfo displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+ assertThat(matches(defaultMode, displayMode1)).isTrue();
+ assertThat(matches(displayDevice.getSystemPreferredDisplayModeLocked(), displayMode1))
+ .isTrue();
+
+ display.dynamicInfo.preferredBootDisplayMode = 1;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice changedDisplayDevice = mListener.changedDisplays.get(0);
+ changedDisplayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = changedDisplayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(modes.length);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode1);
+ assertModeIsSupported(displayDeviceInfo.supportedModes, displayMode2);
+
+ assertThat(
+ matches(changedDisplayDevice.getSystemPreferredDisplayModeLocked(), displayMode2))
+ .isTrue();
+ }
+
+ @Test
public void testHdrSdrRatio_notifiesOnChange() throws Exception {
FakeDisplay display = new FakeDisplay(PORT_A);
setUpDisplay(display);
@@ -1230,7 +1276,7 @@ public class LocalDisplayAdapterTest {
private void assertModeIsSupported(Display.Mode[] supportedModes,
SurfaceControl.DisplayMode mode) {
assertThat(Arrays.stream(supportedModes).anyMatch(
- x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+ x -> x.matches(mode.width, mode.height, mode.peakRefreshRate))).isTrue();
}
private void assertModeIsSupported(Display.Mode[] supportedModes,
@@ -1244,7 +1290,7 @@ public class LocalDisplayAdapterTest {
+ Arrays.toString(supportedModes);
Truth.assertWithMessage(message)
.that(Arrays.stream(supportedModes)
- .anyMatch(x -> x.matches(mode.width, mode.height, mode.refreshRate)
+ .anyMatch(x -> x.matches(mode.width, mode.height, mode.peakRefreshRate)
&& Arrays.equals(x.getAlternativeRefreshRates(), sortedAlternativeRates)))
.isTrue();
}
@@ -1332,16 +1378,28 @@ public class LocalDisplayAdapterTest {
private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
float refreshRate) {
- return createFakeDisplayMode(id, width, height, refreshRate, /* group */ 0);
+ return createFakeDisplayMode(id, width, height, refreshRate, refreshRate);
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate,
+ float vsyncRate) {
+ return createFakeDisplayMode(id, width, height, refreshRate, vsyncRate, /* group */ 0);
+ }
+
+ private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
+ float refreshRate, int group) {
+ return createFakeDisplayMode(id, width, height, refreshRate, refreshRate, group);
}
private static SurfaceControl.DisplayMode createFakeDisplayMode(int id, int width, int height,
- float refreshRate, int group) {
+ float refreshRate, float vsyncRate, int group) {
final SurfaceControl.DisplayMode mode = new SurfaceControl.DisplayMode();
mode.id = id;
mode.width = width;
mode.height = height;
- mode.refreshRate = refreshRate;
+ mode.peakRefreshRate = refreshRate;
+ mode.vsyncRate = vsyncRate;
mode.xDpi = 100;
mode.yDpi = 100;
mode.group = group;
@@ -1444,6 +1502,9 @@ public class LocalDisplayAdapterTest {
private boolean matches(Display.Mode a, SurfaceControl.DisplayMode b) {
return a.getPhysicalWidth() == b.width && a.getPhysicalHeight() == b.height
- && Float.floatToIntBits(a.getRefreshRate()) == Float.floatToIntBits(b.refreshRate);
+ && Float.floatToIntBits(a.getRefreshRate())
+ == Float.floatToIntBits(b.peakRefreshRate)
+ && Float.floatToIntBits(a.getVsyncRate())
+ == Float.floatToIntBits(b.vsyncRate);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index c0e0df98213f..ff2b1f466a5c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -137,8 +137,10 @@ public class BrightnessClamperControllerTest {
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(false);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -150,6 +152,7 @@ public class BrightnessClamperControllerTest {
assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(0,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(-1, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertEquals(initialSlowChange, state.isSlowChange());
}
@@ -158,8 +161,10 @@ public class BrightnessClamperControllerTest {
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -171,6 +176,7 @@ public class BrightnessClamperControllerTest {
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertFalse(state.isSlowChange());
}
@@ -179,8 +185,10 @@ public class BrightnessClamperControllerTest {
float initialBrightness = 0.6f;
boolean initialSlowChange = true;
float clampedBrightness = 0.8f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -192,6 +200,7 @@ public class BrightnessClamperControllerTest {
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertFalse(state.isSlowChange());
}
@@ -200,8 +209,10 @@ public class BrightnessClamperControllerTest {
float initialBrightness = 0.8f;
boolean initialSlowChange = true;
float clampedBrightness = 0.6f;
+ float customAnimationRate = 0.01f;
when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness);
when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL);
+ when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate);
when(mMockClamper.isActive()).thenReturn(true);
mTestInjector.mCapturedChangeListener.onChanged();
mTestHandler.flush();
@@ -216,6 +227,7 @@ public class BrightnessClamperControllerTest {
assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
assertEquals(BrightnessReason.MODIFIER_THROTTLED,
state.getBrightnessReason().getModifier() & BrightnessReason.MODIFIER_THROTTLED);
+ assertEquals(customAnimationRate, state.getCustomAnimationRate(), FLOAT_TOLERANCE);
assertEquals(initialSlowChange, state.isSlowChange());
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index ee187baf524e..8d8274c61b20 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -56,9 +56,9 @@ public class HdrClamperTest {
private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
Map.of(500f, 0.6f),
/* brightnessIncreaseDebounceMillis= */ 1000,
- /* brightnessIncreaseDurationMillis= */ 2000,
+ /* screenBrightnessRampIncrease= */ 0.02f,
/* brightnessDecreaseDebounceMillis= */ 3000,
- /* brightnessDecreaseDurationMillis= */4000
+ /* screenBrightnessRampDecrease= */0.04f
);
private static final int WIDTH = 600;
@@ -152,8 +152,7 @@ public class HdrClamperTest {
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
- assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.04, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -181,8 +180,7 @@ public class HdrClamperTest {
mClock.fastForward(1000);
mTestHandler.timeAdvance();
assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 2
- assertEquals(0.047137f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.02f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
@Test
@@ -209,8 +207,7 @@ public class HdrClamperTest {
mClock.fastForward(3000);
mTestHandler.timeAdvance();
assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
- // 0.6 to HLG = 0.905727, rate = (1-0.905727) / 4
- assertEquals(0.023568f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
+ assertEquals(0.04f, mHdrClamper.getTransitionRate(), FLOAT_TOLERANCE);
}
// MsgInfo.sendTime is calculated first by adding SystemClock.uptimeMillis()
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index c493f8479045..37fe8d1d7fff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -2617,6 +2617,31 @@ public class MockingOomAdjusterTests {
assertTrue(CACHED_APP_MAX_ADJ >= app3.mState.getSetAdj());
}
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testUpdateOomAdj_DoOne_AboveClient_NotStarted() {
+ ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID,
+ MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, true));
+ doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
+ doReturn(app).when(sService).getTopApp();
+ sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+
+ assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+
+ // Start binding to a service that isn't running yet.
+ ServiceRecord sr = makeServiceRecord(app);
+ sr.app = null;
+ bindService(null, app, sr, Context.BIND_ABOVE_CLIENT, mock(IBinder.class));
+
+ // Since sr.app is null, this service cannot be in the same process as the
+ // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect.
+ app.mServices.updateHasAboveClientLocked();
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+ assertTrue(app.mServices.hasAboveClient());
+ assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
+ }
+
private ProcessRecord makeDefaultProcessRecord(int pid, int uid, String processName,
String packageName, boolean hasShownUi) {
long now = SystemClock.uptimeMillis();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 2f6859c14742..be33b1b6f34f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -165,8 +165,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
null
}
whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
- nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
- nullable(), nullable(), nullable(), nullable(), nullable(), nullable())) {
+ nullable(), nullable(), nullable())) {
val name: String = getArgument(0)
val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
?: return@whenever null
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 610ea903767e..7a6ac4ef8f26 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
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.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
@@ -44,6 +45,8 @@ import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -122,6 +125,8 @@ public class PackageArchiverTest {
private PackageSetting mPackageSetting;
+ private PackageManagerService mPackageManagerService;
+
private PackageArchiver mArchiveManager;
@Before
@@ -130,7 +135,7 @@ public class PackageArchiverTest {
rule.system().stageNominalSystemState();
when(rule.mocks().getInjector().getPackageInstallerService()).thenReturn(
mInstallerService);
- PackageManagerService pm = spy(new PackageManagerService(rule.mocks().getInjector(),
+ mPackageManagerService = spy(new PackageManagerService(rule.mocks().getInjector(),
/* factoryTest= */false,
MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
/* isEngBuild= */ false,
@@ -154,15 +159,18 @@ public class PackageArchiverTest {
when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
mLauncherActivityInfos);
- doReturn(mComputer).when(pm).snapshotComputer();
+ doReturn(mComputer).when(mPackageManagerService).snapshotComputer();
when(mComputer.getPackageUid(eq(CALLER_PACKAGE), eq(0L), eq(mUserId))).thenReturn(
Binder.getCallingUid());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
mock(Resources.class));
+ doReturn(new ParceledListSlice<>(List.of(mock(ResolveInfo.class))))
+ .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
+ eq(mUserId));
- mArchiveManager = spy(new PackageArchiver(mContext, pm));
+ mArchiveManager = spy(new PackageArchiver(mContext, mPackageManagerService));
doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
any(LauncherActivityInfo.class), eq(mUserId), anyInt());
doReturn(mIcon).when(mArchiveManager).decodeIcon(
@@ -237,6 +245,21 @@ public class PackageArchiverTest {
}
@Test
+ public void archiveApp_installerDoesntSupportUnarchival() {
+ doReturn(new ParceledListSlice<>(List.of()))
+ .when(mPackageManagerService).queryIntentReceivers(any(), any(), any(), anyLong(),
+ eq(mUserId));
+
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ "Installer does not support unarchival");
+ }
+
+ @Test
public void archiveApp_noMainActivities() {
when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
List.of());
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index eefe5af314a6..3dbab1360af4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -462,7 +462,7 @@ public class WallpaperManagerServiceTests {
wallpaper.wallpaperObserver.stopWatching();
spyOn(wallpaper.wallpaperObserver);
- doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
+ doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(false);
wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
// ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 5cc84b197e03..78bf9b0bc828 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -24,7 +24,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -44,7 +43,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
-import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionAllowlistManager;
import com.android.server.contentprotection.ContentProtectionConsentManager;
import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.pm.UserManagerInternal;
@@ -94,7 +93,7 @@ public class ContentCaptureManagerServiceTest {
@Mock private UserManagerInternal mMockUserManagerInternal;
- @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+ @Mock private ContentProtectionAllowlistManager mMockContentProtectionAllowlistManager;
@Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo;
@@ -108,7 +107,7 @@ public class ContentCaptureManagerServiceTest {
private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
- private int mContentProtectionBlocklistManagersCreated;
+ private int mContentProtectionAllowlistManagersCreated;
private int mContentProtectionServiceInfosCreated;
@@ -132,10 +131,10 @@ public class ContentCaptureManagerServiceTest {
@Test
public void constructor_contentProtection_flagDisabled_noManagers() {
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -145,10 +144,10 @@ public class ContentCaptureManagerServiceTest {
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -158,10 +157,10 @@ public class ContentCaptureManagerServiceTest {
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(0);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(0);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ verifyZeroInteractions(mMockContentProtectionAllowlistManager);
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@@ -171,26 +170,13 @@ public class ContentCaptureManagerServiceTest {
mContentCaptureManagerService = new TestContentCaptureManagerService();
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+ assertThat(mContentProtectionAllowlistManagersCreated).isEqualTo(1);
assertThat(mContentProtectionConsentManagersCreated).isEqualTo(1);
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
- verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
verifyZeroInteractions(mMockContentProtectionConsentManager);
}
@Test
- public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
- mDevCfgEnableContentProtectionReceiver = true;
- mContentCaptureManagerService = new TestContentCaptureManagerService();
- mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
- verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
-
- mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
-
- verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
- }
-
- @Test
public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -201,13 +187,13 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isNull();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -239,13 +225,13 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
assertThat(actual.whitelistedComponents).isNull();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
@@ -273,7 +259,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -287,13 +273,13 @@ public class ContentCaptureManagerServiceTest {
USER_ID, PACKAGE_NAME);
assertThat(actual).isFalse();
- verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
}
@Test
public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -317,7 +303,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -331,7 +317,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager).isConsentGranted(USER_ID);
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -345,13 +331,13 @@ public class ContentCaptureManagerServiceTest {
USER_ID, COMPONENT_NAME);
assertThat(actual).isFalse();
- verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ verify(mMockContentProtectionAllowlistManager).isAllowed(PACKAGE_NAME);
}
@Test
public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -375,13 +361,13 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isTrue();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
public void isContentProtectionReceiverEnabled_true() {
when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
- when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ when(mMockContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -400,7 +386,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -415,7 +401,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -431,7 +417,7 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isFalse();
verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
- verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ verify(mMockContentProtectionAllowlistManager, never()).isAllowed(anyString());
}
@Test
@@ -572,9 +558,9 @@ public class ContentCaptureManagerServiceTest {
}
@Override
- protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
- mContentProtectionBlocklistManagersCreated++;
- return mMockContentProtectionBlocklistManager;
+ protected ContentProtectionAllowlistManager createContentProtectionAllowlistManager() {
+ mContentProtectionAllowlistManagersCreated++;
+ return mMockContentProtectionAllowlistManager;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
new file mode 100644
index 000000000000..6767a85035fe
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionAllowlistManagerTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+/**
+ * Test for {@link ContentProtectionAllowlistManager}.
+ *
+ * <p>Run with: {@code atest FrameworksServicesTests:
+ * com.android.server.contentprotection.ContentProtectionAllowlistManagerTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionAllowlistManagerTest {
+
+ private static final String PACKAGE_NAME = "com.test.package.name";
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private ContentProtectionAllowlistManager mContentProtectionAllowlistManager;
+
+ @Before
+ public void setup() {
+ mContentProtectionAllowlistManager = new ContentProtectionAllowlistManager();
+ }
+
+ @Test
+ public void isAllowed() {
+ boolean actual = mContentProtectionAllowlistManager.isAllowed(PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
deleted file mode 100644
index ba9956a63dca..000000000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionBlocklistManagerTest.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageInfo;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test for {@link ContentProtectionBlocklistManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:
- * com.android.server.contentprotection.ContentProtectionBlocklistManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionBlocklistManagerTest {
-
- private static final String FIRST_PACKAGE_NAME = "com.test.first.package.name";
-
- private static final String SECOND_PACKAGE_NAME = "com.test.second.package.name";
-
- private static final String UNLISTED_PACKAGE_NAME = "com.test.unlisted.package.name";
-
- private static final String PACKAGE_NAME_BLOCKLIST_FILENAME =
- "/product/etc/res/raw/content_protection/package_name_blocklist.txt";
-
- private static final PackageInfo PACKAGE_INFO = new PackageInfo();
-
- private static final List<String> LINES =
- ImmutableList.of(FIRST_PACKAGE_NAME, SECOND_PACKAGE_NAME);
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Mock private ContentProtectionPackageManager mMockContentProtectionPackageManager;
-
- private final List<String> mReadRawFiles = new ArrayList<>();
-
- private ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
-
- @Before
- public void setup() {
- mContentProtectionBlocklistManager = new TestContentProtectionBlocklistManager();
- }
-
- @Test
- public void isAllowed_blocklistNotLoaded() {
- boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- assertThat(mReadRawFiles).isEmpty();
- verifyZeroInteractions(mMockContentProtectionPackageManager);
- }
-
- @Test
- public void isAllowed_inBlocklist() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verifyZeroInteractions(mMockContentProtectionPackageManager);
- }
-
- @Test
- public void isAllowed_packageInfoNotFound() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(null);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never())
- .hasRequestedInternetPermissions(any());
- verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_notRequestedInternet() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(false);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never()).isSystemApp(any());
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_systemApp() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- verify(mMockContentProtectionPackageManager, never()).isUpdatedSystemApp(any());
- }
-
- @Test
- public void isAllowed_updatedSystemApp() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(true);
- when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
- .thenReturn(true);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isAllowed_allowed() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size());
- when(mMockContentProtectionPackageManager.getPackageInfo(UNLISTED_PACKAGE_NAME))
- .thenReturn(PACKAGE_INFO);
- when(mMockContentProtectionPackageManager.hasRequestedInternetPermissions(PACKAGE_INFO))
- .thenReturn(true);
- when(mMockContentProtectionPackageManager.isSystemApp(PACKAGE_INFO)).thenReturn(false);
- when(mMockContentProtectionPackageManager.isUpdatedSystemApp(PACKAGE_INFO))
- .thenReturn(false);
-
- boolean actual = mContentProtectionBlocklistManager.isAllowed(UNLISTED_PACKAGE_NAME);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void updateBlocklist_negativeSize() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ -1);
- assertThat(mReadRawFiles).isEmpty();
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_zeroSize() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 0);
- assertThat(mReadRawFiles).isEmpty();
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(FIRST_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_positiveSize_belowTotal() {
- mContentProtectionBlocklistManager.updateBlocklist(/* blocklistSize= */ 1);
- assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager).getPackageInfo(SECOND_PACKAGE_NAME);
- }
-
- @Test
- public void updateBlocklist_positiveSize_aboveTotal() {
- mContentProtectionBlocklistManager.updateBlocklist(LINES.size() + 1);
- assertThat(mReadRawFiles).containsExactly(PACKAGE_NAME_BLOCKLIST_FILENAME);
-
- mContentProtectionBlocklistManager.isAllowed(FIRST_PACKAGE_NAME);
- mContentProtectionBlocklistManager.isAllowed(SECOND_PACKAGE_NAME);
-
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(FIRST_PACKAGE_NAME);
- verify(mMockContentProtectionPackageManager, never()).getPackageInfo(SECOND_PACKAGE_NAME);
- }
-
- private final class TestContentProtectionBlocklistManager
- extends ContentProtectionBlocklistManager {
-
- TestContentProtectionBlocklistManager() {
- super(mMockContentProtectionPackageManager);
- }
-
- @Override
- protected List<String> readLinesFromRawFile(@NonNull String filename) {
- mReadRawFiles.add(filename);
- return LINES;
- }
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java b/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
deleted file mode 100644
index 7d45ea4ce39a..000000000000
--- a/services/tests/servicestests/src/com/android/server/contentprotection/ContentProtectionPackageManagerTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS 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.contentprotection;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import android.Manifest.permission;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PackageManager.PackageInfoFlags;
-import android.testing.TestableContext;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-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;
-
-/**
- * Test for {@link ContentProtectionPackageManager}.
- *
- * <p>Run with: {@code atest
- * FrameworksServicesTests:com.android.server.contentprotection.ContentProtectionPackageManagerTest}
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ContentProtectionPackageManagerTest {
- private static final String PACKAGE_NAME = "PACKAGE_NAME";
-
- private static final PackageInfo EMPTY_PACKAGE_INFO = new PackageInfo();
-
- private static final PackageInfo SYSTEM_APP_PACKAGE_INFO = createSystemAppPackageInfo();
-
- private static final PackageInfo UPDATED_SYSTEM_APP_PACKAGE_INFO =
- createUpdatedSystemAppPackageInfo();
-
- @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
-
- @Rule
- public final TestableContext mContext =
- new TestableContext(ApplicationProvider.getApplicationContext());
-
- @Mock private PackageManager mMockPackageManager;
-
- private ContentProtectionPackageManager mContentProtectionPackageManager;
-
- @Before
- public void setup() {
- mContext.setMockPackageManager(mMockPackageManager);
- mContentProtectionPackageManager = new ContentProtectionPackageManager(mContext);
- }
-
- @Test
- public void getPackageInfo_found() throws Exception {
- PackageInfo expected = createPackageInfo(/* flags= */ 0);
- when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
- .thenReturn(expected);
-
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isEqualTo(expected);
- }
-
- @Test
- public void getPackageInfo_notFound() throws Exception {
- when(mMockPackageManager.getPackageInfo(eq(PACKAGE_NAME), any(PackageInfoFlags.class)))
- .thenThrow(new NameNotFoundException());
-
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isNull();
- }
-
- @Test
- public void getPackageInfo_null() {
- PackageInfo actual = mContentProtectionPackageManager.getPackageInfo(PACKAGE_NAME);
-
- assertThat(actual).isNull();
- }
-
- @Test
- public void isSystemApp_true() {
- boolean actual = mContentProtectionPackageManager.isSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void isSystemApp_false() {
- boolean actual =
- mContentProtectionPackageManager.isSystemApp(UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isSystemApp_noApplicationInfo() {
- boolean actual = mContentProtectionPackageManager.isSystemApp(EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isUpdatedSystemApp_true() {
- boolean actual =
- mContentProtectionPackageManager.isUpdatedSystemApp(
- UPDATED_SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void isUpdatedSystemApp_false() {
- boolean actual =
- mContentProtectionPackageManager.isUpdatedSystemApp(SYSTEM_APP_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void isUpdatedSystemApp_noApplicationInfo() {
- boolean actual = mContentProtectionPackageManager.isUpdatedSystemApp(EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void hasRequestedInternetPermissions_true() {
- PackageInfo packageInfo = createPackageInfo(new String[] {permission.INTERNET});
-
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
- assertThat(actual).isTrue();
- }
-
- @Test
- public void hasRequestedInternetPermissions_false() {
- PackageInfo packageInfo = createPackageInfo(new String[] {permission.ACCESS_FINE_LOCATION});
-
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(packageInfo);
-
- assertThat(actual).isFalse();
- }
-
- @Test
- public void hasRequestedInternetPermissions_noRequestedPermissions() {
- boolean actual =
- mContentProtectionPackageManager.hasRequestedInternetPermissions(
- EMPTY_PACKAGE_INFO);
-
- assertThat(actual).isFalse();
- }
-
- private static PackageInfo createSystemAppPackageInfo() {
- return createPackageInfo(ApplicationInfo.FLAG_SYSTEM);
- }
-
- private static PackageInfo createUpdatedSystemAppPackageInfo() {
- return createPackageInfo(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
- }
-
- private static PackageInfo createPackageInfo(int flags) {
- return createPackageInfo(flags, /* requestedPermissions= */ new String[0]);
- }
-
- private static PackageInfo createPackageInfo(String[] requestedPermissions) {
- return createPackageInfo(/* flags= */ 0, requestedPermissions);
- }
-
- private static PackageInfo createPackageInfo(int flags, String[] requestedPermissions) {
- PackageInfo packageInfo = new PackageInfo();
- packageInfo.packageName = PACKAGE_NAME;
- packageInfo.applicationInfo = new ApplicationInfo();
- packageInfo.applicationInfo.packageName = PACKAGE_NAME;
- packageInfo.applicationInfo.flags = flags;
- packageInfo.requestedPermissions = requestedPermissions;
- return packageInfo;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 6bfd93b66f61..4bb7d63995ac 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -4,6 +4,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static com.android.server.job.JobStore.JOB_FILE_SPLIT_PREFIX;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,6 +48,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.nio.file.Files;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.ArrayList;
@@ -209,6 +212,43 @@ public class JobStoreTest {
assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
}
+ @Test
+ public void testSkipExtraFiles() throws Exception {
+ setUseSplitFiles(true);
+ final JobInfo task1 = new Builder(8, mComponent)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(10000L)
+ .setRequiresCharging(true)
+ .setPersisted(true)
+ .build();
+ final JobInfo task2 = new Builder(12, mComponent)
+ .setMinimumLatency(5000L)
+ .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setOverrideDeadline(30000L)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPersisted(true)
+ .build();
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
+ runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+ final File rootDir = new File(mTestContext.getFilesDir(), "system/job");
+ final File file1 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml");
+ final File file2 = new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid2 + ".xml");
+
+ Files.copy(file1.toPath(),
+ new File(rootDir, JOB_FILE_SPLIT_PREFIX + uid1 + ".xml.bak").toPath());
+ Files.copy(file1.toPath(), new File(rootDir, "random.xml").toPath());
+ Files.copy(file2.toPath(),
+ new File(rootDir, "blah" + JOB_FILE_SPLIT_PREFIX + uid1 + ".xml").toPath());
+
+ JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
+ }
+
/**
* Test that dynamic constraints aren't written to disk.
*/
@@ -254,22 +294,22 @@ public class JobStoreTest {
file = new File(mTestContext.getFilesDir(), "10000");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX);
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "text.xml");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + ".xml");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "-10123.xml");
assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "1.xml");
assertEquals(1, JobStore.extractUidFromJobFileName(file));
- file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+ file = new File(mTestContext.getFilesDir(), JOB_FILE_SPLIT_PREFIX + "101023.xml");
assertEquals(101023, JobStore.extractUidFromJobFileName(file));
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 37a6d22f038b..eca19c8e8c4d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -255,7 +255,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
public void testUnlockUserKeyIfUnsecuredPassesPrimaryUserAuthSecret() throws RemoteException {
initSpAndSetCredential(PRIMARY_USER_ID, newPassword(null));
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@@ -267,7 +267,7 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@@ -285,39 +285,39 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testHeadlessSystemUserDoesNotPassAuthSecret() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
}
@Test
public void testHeadlessSecondaryUserPassesAuthSecret() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
}
@Test
public void testHeadlessTertiaryUserPassesSameAuthSecret() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
var captor = ArgumentCaptor.forClass(byte[].class);
verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
var value = captor.getValue();
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
}
@Test
public void testHeadlessTertiaryUserPassesSameAuthSecretAfterReset() throws RemoteException {
setupHeadlessTest();
- mLocalService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(SECONDARY_USER_ID);
var captor = ArgumentCaptor.forClass(byte[].class);
verify(mAuthSecretService).setPrimaryUserCredential(captor.capture());
var value = captor.getValue();
mService.clearAuthSecret();
reset(mAuthSecretService);
- mLocalService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
+ mService.unlockUserKeyIfUnsecured(TERTIARY_USER_ID);
verify(mAuthSecretService).setPrimaryUserCredential(eq(value));
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index f94aff706a67..4e6dd064165d 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -26,10 +26,10 @@ import static android.media.projection.ReviewGrantedConsentResult.UNKNOWN;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -67,7 +68,6 @@ import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.testutils.OffsettableClock;
import com.android.server.wm.WindowManagerInternal;
@@ -133,7 +133,7 @@ public class MediaProjectionManagerServiceTest {
private final MediaProjectionManagerService.Injector mMediaProjectionMetricsLoggerInjector =
new MediaProjectionManagerService.Injector() {
@Override
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger() {
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger(Context context) {
return mMediaProjectionMetricsLogger;
}
};
@@ -311,6 +311,70 @@ public class MediaProjectionManagerServiceTest {
}
@Test
+ public void stop_noActiveProjections_doesNotLog() throws Exception {
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+
+ projection.stop();
+
+ verifyZeroInteractions(mMediaProjectionMetricsLogger);
+ }
+
+ @Test
+ public void stop_noSession_logsHostUidAndUnknownTargetUid() throws Exception {
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+
+ projection.stop();
+
+ verify(mMediaProjectionMetricsLogger)
+ .logStopped(UID, ContentRecordingSession.TARGET_UID_UNKNOWN);
+ }
+
+ @Test
+ public void stop_displaySession_logsHostUidAndUnknownTargetUidFullScreen() throws Exception {
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true)
+ .when(mWindowManagerInternal)
+ .setContentRecordingSession(any(ContentRecordingSession.class));
+ service.setContentRecordingSession(DISPLAY_SESSION);
+
+ projection.stop();
+
+ verify(mMediaProjectionMetricsLogger)
+ .logStopped(UID, ContentRecordingSession.TARGET_UID_FULL_SCREEN);
+ }
+
+ @Test
+ public void stop_taskSession_logsHostUidAndTargetUid() throws Exception {
+ int targetUid = 1234;
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true)
+ .when(mWindowManagerInternal)
+ .setContentRecordingSession(any(ContentRecordingSession.class));
+ ContentRecordingSession taskSession =
+ ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid);
+ service.setContentRecordingSession(taskSession);
+
+ projection.stop();
+
+ verify(mMediaProjectionMetricsLogger).logStopped(UID, targetUid);
+ }
+
+ @Test
public void testIsValid_multipleStarts_preventionDisabled() throws NameNotFoundException {
MediaProjectionManagerService service = new MediaProjectionManagerService(mContext,
mPreventReusedTokenDisabledInjector);
@@ -586,6 +650,40 @@ public class MediaProjectionManagerServiceTest {
/* isSetSessionSuccessful= */ false, RECORD_CANCEL);
}
+ @Test
+ public void notifyPermissionRequestInitiated_forwardsToLogger() {
+ int hostUid = 123;
+ int sessionCreationSource = 456;
+ mService =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+ mService.notifyPermissionRequestInitiated(hostUid, sessionCreationSource);
+
+ verify(mMediaProjectionMetricsLogger).logInitiated(hostUid, sessionCreationSource);
+ }
+
+ @Test
+ public void notifyPermissionRequestDisplayed_forwardsToLogger() {
+ int hostUid = 123;
+ mService =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+ mService.notifyPermissionRequestDisplayed(hostUid);
+
+ verify(mMediaProjectionMetricsLogger).logPermissionRequestDisplayed(hostUid);
+ }
+
+ @Test
+ public void notifyAppSelectorDisplayed_forwardsToLogger() {
+ int hostUid = 456;
+ mService =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+
+ mService.notifyAppSelectorDisplayed(hostUid);
+
+ verify(mMediaProjectionMetricsLogger).logAppSelectorDisplayed(hostUid);
+ }
+
/**
* Executes and validates scenario where the consent result indicates the projection ends.
*/
@@ -749,18 +847,79 @@ public class MediaProjectionManagerServiceTest {
public void setContentRecordingSession_success_logsCaptureInProgress()
throws Exception {
mService.addCallback(mWatcherCallback);
- MediaProjectionManagerService service = new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
- MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
projection.start(mIMediaProjectionCallback);
doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
any(ContentRecordingSession.class));
service.setContentRecordingSession(DISPLAY_SESSION);
- verify(mMediaProjectionMetricsLogger).notifyProjectionStateChange(
+ verify(mMediaProjectionMetricsLogger).logInProgress(
projection.uid,
- MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS,
- FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN
+ DISPLAY_SESSION.getTargetUid()
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_taskSession_logsCaptureInProgressWithTargetUid()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true)
+ .when(mWindowManagerInternal)
+ .setContentRecordingSession(any(ContentRecordingSession.class));
+ int targetUid = 123455;
+
+ ContentRecordingSession taskSession =
+ ContentRecordingSession.createTaskSession(mock(IBinder.class), targetUid);
+ service.setContentRecordingSession(taskSession);
+
+ verify(mMediaProjectionMetricsLogger).logInProgress(projection.uid, targetUid);
+ }
+
+ @Test
+ public void setContentRecordingSession_failure_doesNotLogCaptureInProgress() throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(false).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(DISPLAY_SESSION);
+
+ verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+ anyInt(),
+ anyInt()
+ );
+ }
+
+ @Test
+ public void setContentRecordingSession_sessionNull_doesNotLogCaptureInProgress()
+ throws Exception {
+ mService.addCallback(mWatcherCallback);
+ MediaProjectionManagerService service =
+ new MediaProjectionManagerService(mContext, mMediaProjectionMetricsLoggerInjector);
+ MediaProjectionManagerService.MediaProjection projection =
+ startProjectionPreconditions(service);
+ projection.start(mIMediaProjectionCallback);
+ doReturn(true).when(mWindowManagerInternal).setContentRecordingSession(
+ any(ContentRecordingSession.class));
+
+ service.setContentRecordingSession(null);
+
+ verify(mMediaProjectionMetricsLogger, never()).logInProgress(
+ anyInt(),
+ anyInt()
);
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
new file mode 100644
index 000000000000..410604f02e1f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionMetricsLoggerTest.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.media.projection;
+
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED;
+import static com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Duration;
+
+/**
+ * Tests for the {@link MediaProjectionMetricsLoggerTest} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionMetricsLoggerTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionMetricsLoggerTest {
+
+ private static final int TEST_HOST_UID = 123;
+ private static final int TEST_TARGET_UID = 456;
+ private static final int TEST_CREATION_SOURCE = 789;
+
+ @Mock private FrameworkStatsLogWrapper mFrameworkStatsLogWrapper;
+ @Mock private MediaProjectionSessionIdGenerator mSessionIdGenerator;
+ @Mock private MediaProjectionTimestampStore mTimestampStore;
+
+ private MediaProjectionMetricsLogger mLogger;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mLogger =
+ new MediaProjectionMetricsLogger(
+ mFrameworkStatsLogWrapper, mSessionIdGenerator, mTimestampStore);
+ }
+
+ @Test
+ public void logInitiated_logsStateChangedAtomId() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logInitiated_logsStateInitiated() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+ }
+
+ @Test
+ public void logInitiated_logsHostUid() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logInitiated_logsSessionCreationSource() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifyCreationSourceLogged(TEST_CREATION_SOURCE);
+ }
+
+ @Test
+ public void logInitiated_logsUnknownTargetUid() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifyTargetUidLogged(-2);
+ }
+
+ @Test
+ public void logInitiated_noPreviousSession_logsUnknownTimeSinceLastActive() {
+ when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(null);
+
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logInitiated_previousSession_logsTimeSinceLastActiveInSeconds() {
+ Duration timeSinceLastActiveSession = Duration.ofHours(1234);
+ when(mTimestampStore.timeSinceLastActiveSession()).thenReturn(timeSinceLastActiveSession);
+
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifyTimeSinceLastActiveSessionLogged((int) timeSinceLastActiveSession.toSeconds());
+ }
+
+ @Test
+ public void logInitiated_logsNewSessionId() {
+ int newSessionId = 123;
+ when(mSessionIdGenerator.createAndGetNewSessionId()).thenReturn(newSessionId);
+
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+
+ verifySessionIdLogged(newSessionId);
+ }
+
+ @Test
+ public void logInitiated_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+ }
+
+ @Test
+ public void logStopped_logsStateChangedAtomId() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logStopped_logsCurrentSessionId() {
+ int currentSessionId = 987;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logStopped_logsStateStopped() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateLogged(MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ @Test
+ public void logStopped_logsHostUid() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logStopped_logsTargetUid() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTargetUidLogged(TEST_TARGET_UID);
+ }
+
+ @Test
+ public void logStopped_logsUnknownTimeSinceLastActive() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logStopped_logsUnknownSessionCreationSource() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logStopped_logsPreviousState() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+ }
+
+ @Test
+ public void logStopped_capturingWasInProgress_registersActiveSessionEnded() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verify(mTimestampStore).registerActiveSessionEnded();
+ }
+
+ @Test
+ public void logStopped_capturingWasNotInProgress_doesNotRegistersActiveSessionEnded() {
+ mLogger.logStopped(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verify(mTimestampStore, never()).registerActiveSessionEnded();
+ }
+
+ @Test
+ public void logInProgress_logsStateChangedAtomId() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logInProgress_logsCurrentSessionId() {
+ int currentSessionId = 987;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logInProgress_logsStateInProgress() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+ }
+
+ @Test
+ public void logInProgress_logsHostUid() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logInProgress_logsTargetUid() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTargetUidLogged(TEST_TARGET_UID);
+ }
+
+ @Test
+ public void logInProgress_logsUnknownTimeSinceLastActive() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logInProgress_logsUnknownSessionCreationSource() {
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logInProgress_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_TARGET_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_CAPTURING_IN_PROGRESS);
+
+ mLogger.logInProgress(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsStateChangedAtomId() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsCurrentSessionId() {
+ int currentSessionId = 765;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsStateDisplayed() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsHostUid() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownTargetUid() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyTargetUidLogged(-2);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownTimeSinceLastActive() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsUnknownSessionCreationSource() {
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logPermissionRequestDisplayed_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_PERMISSION_REQUEST_DISPLAYED);
+
+ mLogger.logPermissionRequestDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsStateChangedAtomId() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyStateChangedAtomIdLogged();
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsCurrentSessionId() {
+ int currentSessionId = 765;
+ when(mSessionIdGenerator.getCurrentSessionId()).thenReturn(currentSessionId);
+
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifySessionIdLogged(currentSessionId);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsStateDisplayed() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsHostUid() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyHostUidLogged(TEST_HOST_UID);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsUnknownTargetUid() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyTargetUidLogged(-2);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsUnknownTimeSinceLastActive() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyTimeSinceLastActiveSessionLogged(-1);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsUnknownSessionCreationSource() {
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+
+ verifyCreationSourceLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN);
+ }
+
+ @Test
+ public void logAppSelectorDisplayed_logsPreviousState() {
+ mLogger.logInitiated(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN);
+
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED);
+
+ mLogger.logStopped(TEST_HOST_UID, TEST_CREATION_SOURCE);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED);
+
+ mLogger.logAppSelectorDisplayed(TEST_HOST_UID);
+ verifyPreviousStateLogged(
+ MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_STOPPED);
+ }
+
+ private void verifyStateChangedAtomIdLogged() {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ eq(FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED),
+ /* sessionId= */ anyInt(),
+ /* state= */ anyInt(),
+ /* previousState= */ anyInt(),
+ /* hostUid= */ anyInt(),
+ /* targetUid= */ anyInt(),
+ /* timeSinceLastActive= */ anyInt(),
+ /* creationSource= */ anyInt());
+ }
+
+ private void verifyStateLogged(int state) {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ anyInt(),
+ /* sessionId= */ anyInt(),
+ eq(state),
+ /* previousState= */ anyInt(),
+ /* hostUid= */ anyInt(),
+ /* targetUid= */ anyInt(),
+ /* timeSinceLastActive= */ anyInt(),
+ /* creationSource= */ anyInt());
+ }
+
+ private void verifyHostUidLogged(int hostUid) {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ anyInt(),
+ /* sessionId= */ anyInt(),
+ /* state= */ anyInt(),
+ /* previousState= */ anyInt(),
+ eq(hostUid),
+ /* targetUid= */ anyInt(),
+ /* timeSinceLastActive= */ anyInt(),
+ /* creationSource= */ anyInt());
+ }
+
+ private void verifyCreationSourceLogged(int creationSource) {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ anyInt(),
+ /* sessionId= */ anyInt(),
+ /* state= */ anyInt(),
+ /* previousState= */ anyInt(),
+ /* hostUid= */ anyInt(),
+ /* targetUid= */ anyInt(),
+ /* timeSinceLastActive= */ anyInt(),
+ eq(creationSource));
+ }
+
+ private void verifyTargetUidLogged(int targetUid) {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ anyInt(),
+ /* sessionId= */ anyInt(),
+ /* state= */ anyInt(),
+ /* previousState= */ anyInt(),
+ /* hostUid= */ anyInt(),
+ eq(targetUid),
+ /* timeSinceLastActive= */ anyInt(),
+ /* creationSource= */ anyInt());
+ }
+
+ private void verifyTimeSinceLastActiveSessionLogged(int timeSinceLastActiveSession) {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ anyInt(),
+ /* sessionId= */ anyInt(),
+ /* state= */ anyInt(),
+ /* previousState= */ anyInt(),
+ /* hostUid= */ anyInt(),
+ /* targetUid= */ anyInt(),
+ /* timeSinceLastActive= */ eq(timeSinceLastActiveSession),
+ /* creationSource= */ anyInt());
+ }
+
+ private void verifySessionIdLogged(int newSessionId) {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ anyInt(),
+ /* sessionId= */ eq(newSessionId),
+ /* state= */ anyInt(),
+ /* previousState= */ anyInt(),
+ /* hostUid= */ anyInt(),
+ /* targetUid= */ anyInt(),
+ /* timeSinceLastActive= */ anyInt(),
+ /* creationSource= */ anyInt());
+ }
+
+ private void verifyPreviousStateLogged(int previousState) {
+ verify(mFrameworkStatsLogWrapper)
+ .write(
+ /* code= */ anyInt(),
+ /* sessionId= */ anyInt(),
+ /* state= */ anyInt(),
+ eq(previousState),
+ /* hostUid= */ anyInt(),
+ /* targetUid= */ anyInt(),
+ /* timeSinceLastActive= */ anyInt(),
+ /* creationSource= */ anyInt());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index 42be3d36620a..5d6f36c005b4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -19,7 +19,6 @@ package com.android.server.pm;
import android.content.pm.SigningDetails;
import android.util.SparseArray;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageUserStateImpl;
@@ -159,17 +158,19 @@ public class PackageSettingBuilder {
public PackageSetting build() {
final PackageSetting packageSetting = new PackageSetting(mName, mRealName,
- new File(mCodePath), mLegacyNativeLibraryPathString, mPrimaryCpuAbiString,
- mSecondaryCpuAbiString, mCpuAbiOverrideString, mPVersionCode, mPkgFlags,
- mPrivateFlags, mSharedUserId, null /* usesSdkLibraries */,
- null /* usesSdkLibrariesVersions */, null /* usesStaticLibraries */,
- null /* usesStaticLibrariesVersions */, mMimeGroups, mDomainSetId);
- packageSetting.setSignatures(mSigningDetails != null
- ? new PackageSignatures(mSigningDetails)
- : new PackageSignatures());
- packageSetting.setPkg((ParsedPackage) mPkg);
- packageSetting.setAppId(mAppId);
- packageSetting.setVolumeUuid(this.mVolumeUuid);
+ new File(mCodePath), mPkgFlags, mPrivateFlags, mDomainSetId)
+ .setLegacyNativeLibraryPath(mLegacyNativeLibraryPathString)
+ .setPrimaryCpuAbi(mPrimaryCpuAbiString)
+ .setSecondaryCpuAbi(mSecondaryCpuAbiString)
+ .setCpuAbiOverride(mCpuAbiOverrideString)
+ .setLongVersionCode(mPVersionCode)
+ .setSharedUserAppId(mSharedUserId)
+ .setMimeGroups(mMimeGroups)
+ .setSignatures(mSigningDetails != null
+ ? new PackageSignatures(mSigningDetails) : new PackageSignatures())
+ .setPkg(mPkg)
+ .setAppId(mAppId)
+ .setVolumeUuid(this.mVolumeUuid);
if (mInstallSource != null) {
packageSetting.setInstallSource(mInstallSource);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5dfce0613190..89c6a2201434 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -974,7 +974,6 @@ public final class UserManagerTest {
assertThrows(SecurityException.class, userProps::getAlwaysVisible);
}
-
// Make sure only max managed profiles can be created
@MediumTest
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 9bd938f2e0a7..cf8548cfe689 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -80,7 +80,9 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.view.accessibility.IAccessibilityManagerClient;
+
import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
@@ -93,6 +95,7 @@ import com.android.server.pm.PackageManagerService;
import java.util.List;
import java.util.Objects;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -190,7 +193,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
assertTrue(mAccessibilityManager.isEnabled());
// TODO (b/291907312): remove feature flag
- mTestFlagResolver.setFlagOverride(NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR, true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Disable feature flags by default. Tests should enable as needed.
mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 75d012a8e1f2..6792cfe6e788 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -71,6 +71,7 @@ import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -84,7 +85,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ENABLE_ATTENTION_HELPER_REFACTOR;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
@@ -207,6 +207,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
import android.permission.PermissionManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
@@ -318,6 +319,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final int UID_HEADLESS = 1_000_000;
private static final int TOAST_DURATION = 2_000;
private static final int SECONDARY_DISPLAY_ID = 42;
+ private static final int TEST_PROFILE_USERHANDLE = 12;
private final int mUid = Binder.getCallingUid();
private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
@@ -445,7 +447,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker;
TestableFlagResolver mTestFlagResolver = new TestableFlagResolver();
-
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@Mock
@@ -611,7 +613,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
});
// TODO (b/291907312): remove feature flag
- mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, false);
+ mSetFlagsRule.disableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER,
+ Flags.FLAG_POLITE_NOTIFICATIONS);
initNMS();
}
@@ -652,7 +655,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(mHistoryManager).onBootPhaseAppsCanStart();
// TODO b/291907312: remove feature flag
- if (mTestFlagResolver.isEnabled(ENABLE_ATTENTION_HELPER_REFACTOR)) {
+ if (Flags.refactorAttentionHelper()) {
mService.mAttentionHelper.setAudioManager(mAudioManager);
} else {
mService.setAudioManager(mAudioManager);
@@ -826,6 +829,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mPackageIntentReceiver.onReceive(getContext(), intent);
}
+ private void simulateProfileAvailabilityActions(String intentAction) {
+ final Intent intent = new Intent(intentAction);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE);
+ mUserSwitchIntentReceiver.onReceive(mContext, intent);
+ }
+
private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() {
ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>();
changed.put(true, new ArrayList<>());
@@ -1683,7 +1692,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testEnqueueNotificationWithTag_WritesExpectedLogs_NAHRefactor() throws Exception {
// TODO b/291907312: remove feature flag
- mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Cleanup NMS before re-initializing
if (mService != null) {
try {
@@ -9146,7 +9155,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testOnBubbleMetadataChangedToSuppressNotification_soundStopped_NAHRefactor()
throws Exception {
// TODO b/291907312: remove feature flag
- mTestFlagResolver.setFlagOverride(ENABLE_ATTENTION_HELPER_REFACTOR, true);
+ mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
// Cleanup NMS before re-initializing
if (mService != null) {
try {
@@ -12751,6 +12760,23 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(service, times(1)).setDNDMigrationDone(user.id);
}
+ @Test
+ public void testProfileUnavailableIntent() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
+ verify(mWorkerHandler).post(any(Runnable.class));
+ verify(mSnoozeHelper).clearData(anyInt());
+ }
+
+
+ @Test
+ public void testManagedProfileUnavailableIntent() throws RemoteException {
+ mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ verify(mWorkerHandler).post(any(Runnable.class));
+ verify(mSnoozeHelper).clearData(anyInt());
+ }
+
private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
throws RemoteException {
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
index 121e29609df5..337dd22e8712 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java
@@ -91,7 +91,6 @@ public class NotificationVisitUrisTest extends UiServiceTestCase {
private static final Multimap<Class<?>, String> KNOWN_BAD =
ImmutableMultimap.<Class<?>, String>builder()
.put(Person.Builder.class, "setUri") // TODO: b/281044385
- .put(RemoteViews.class, "setRemoteAdapter") // TODO: b/281044385
.build();
// Types that we can't really produce. No methods receiving these parameters will be invoked.
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index af39b2f027ee..1b8d746f271f 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -60,6 +60,7 @@ android_test {
"truth",
"testables",
"hamcrest-library",
+ "flag-junit",
"platform-compat-test-rules",
"CtsSurfaceValidatorLib",
"service-sdksandbox.impl",
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 8db09f9e3a16..270d5df5e702 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -46,7 +46,6 @@ import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;
import static java.util.Collections.unmodifiableMap;
import android.content.Context;
-import android.os.Looper;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.view.InputDevice;
@@ -99,10 +98,6 @@ class ShortcutKeyTestBase {
* settings values.
*/
protected final void setUpPhoneWindowManager(boolean supportSettingsUpdate) {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
doReturn(mSettingsProviderRule.mockContentResolver(mContext))
.when(mContext).getContentResolver();
mPhoneWindowManager = new TestPhoneWindowManager(mContext, supportSettingsUpdate);
@@ -208,5 +203,6 @@ class ShortcutKeyTestBase {
mPhoneWindowManager.dispatchUnhandledKey(keyEvent);
}
}
+ mPhoneWindowManager.dispatchAllPendingEvents();
}
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index d388db82f2be..f2721a556454 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -25,10 +25,12 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
import android.content.Context;
+import android.os.Looper;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -38,7 +40,9 @@ import android.view.KeyEvent;
import org.junit.Before;
import org.junit.Test;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
@@ -57,6 +61,7 @@ public class SingleKeyGestureTests {
private CountDownLatch mLongPressed = new CountDownLatch(1);
private CountDownLatch mVeryLongPressed = new CountDownLatch(1);
private CountDownLatch mMultiPressed = new CountDownLatch(1);
+ private BlockingQueue<KeyUpData> mKeyUpQueue = new LinkedBlockingQueue<>();
private final Instrumentation mInstrumentation = getInstrumentation();
private final Context mContext = mInstrumentation.getTargetContext();
@@ -76,7 +81,7 @@ public class SingleKeyGestureTests {
public void setUp() {
mInstrumentation.runOnMainSync(
() -> {
- mDetector = SingleKeyGestureDetector.get(mContext);
+ mDetector = SingleKeyGestureDetector.get(mContext, Looper.myLooper());
initSingleKeyGestureRules();
});
@@ -134,6 +139,11 @@ public class SingleKeyGestureTests {
assertTrue(mMaxMultiPressCount >= count);
assertEquals(mExpectedMultiPressCount, count);
}
+
+ @Override
+ void onKeyUp(long eventTime, int multiPressCount) {
+ mKeyUpQueue.add(new KeyUpData(KEYCODE_POWER, multiPressCount));
+ }
});
mDetector.addRule(
@@ -167,12 +177,27 @@ public class SingleKeyGestureTests {
}
@Override
+ void onKeyUp(long eventTime, int multiPressCount) {
+ mKeyUpQueue.add(new KeyUpData(KEYCODE_BACK, multiPressCount));
+ }
+
+ @Override
void onLongPress(long downTime) {
mLongPressed.countDown();
}
});
}
+ private static class KeyUpData {
+ public final int keyCode;
+ public final int pressCount;
+
+ KeyUpData(int keyCode, int pressCount) {
+ this.keyCode = keyCode;
+ this.pressCount = pressCount;
+ }
+ }
+
private void pressKey(int keyCode, long pressTime) {
pressKey(keyCode, pressTime, true /* interactive */);
}
@@ -250,6 +275,21 @@ public class SingleKeyGestureTests {
}
@Test
+ public void testOnKeyUp() throws InterruptedException {
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+
+ verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+ }
+
+ private void verifyKeyUpData(int expectedKeyCode, int expectedMultiPressCount)
+ throws InterruptedException {
+ KeyUpData keyUpData = mKeyUpQueue.poll(mWaitTimeout, TimeUnit.MILLISECONDS);
+ assertNotNull(keyUpData);
+ assertEquals(expectedKeyCode, keyUpData.keyCode);
+ assertEquals(expectedMultiPressCount, keyUpData.pressCount);
+ }
+
+ @Test
public void testNonInteractive() throws InterruptedException {
// Disallow short press behavior from non interactive.
mAllowNonInteractiveForPress = false;
@@ -314,6 +354,33 @@ public class SingleKeyGestureTests {
}
@Test
+ public void testOnKeyUp_Pressure() throws InterruptedException {
+ final HandlerThread handlerThread =
+ new HandlerThread("testInputReader", Process.THREAD_PRIORITY_DISPLAY);
+ handlerThread.start();
+ Handler newHandler = new Handler(handlerThread.getLooper());
+ try {
+ // To make sure we won't get any unexpected multi-press count.
+ for (int i = 0; i < 5; i++) {
+ newHandler.runWithScissors(
+ () -> {
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ pressKey(KEYCODE_POWER, 0 /* pressTime */);
+ },
+ mWaitTimeout);
+ newHandler.runWithScissors(
+ () -> pressKey(KEYCODE_BACK, 0 /* pressTime */), mWaitTimeout);
+
+ verifyKeyUpData(KEYCODE_POWER, 1 /* expectedMultiPressCount */);
+ verifyKeyUpData(KEYCODE_POWER, 2 /* expectedMultiPressCount */);
+ verifyKeyUpData(KEYCODE_BACK, 1 /* expectedMultiPressCount */);
+ }
+ } finally {
+ handlerThread.quitSafely();
+ }
+ }
+
+ @Test
public void testUpdateRule() throws InterruptedException {
// Power key rule doesn't allow the long press gesture.
mLongPressOnPowerBehavior = false;
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 2244dbe8af98..e26260a6836c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -78,6 +78,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.Vibrator;
import android.os.VibratorInfo;
+import android.os.test.TestLooper;
import android.service.dreams.DreamManagerInternal;
import android.telecom.TelecomManager;
import android.util.FeatureFlagUtils;
@@ -117,7 +118,6 @@ import org.mockito.quality.Strictness;
import java.util.function.Supplier;
class TestPhoneWindowManager {
- private static final long SHORTCUT_KEY_DELAY_MILLIS = 150;
private static final long TEST_SINGLE_KEY_DELAY_MILLIS
= SingleKeyGestureDetector.MULTI_PRESS_TIMEOUT + 1000L * HW_TIMEOUT_MULTIPLIER;
@@ -160,12 +160,13 @@ class TestPhoneWindowManager {
@Mock private KeyguardServiceDelegate mKeyguardServiceDelegate;
private StaticMockitoSession mMockitoSession;
+ private TestLooper mTestLooper = new TestLooper();
private HandlerThread mHandlerThread;
private Handler mHandler;
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
- super(context, funcs);
+ super(context, funcs, mTestLooper.getLooper());
}
AccessibilityShortcutController getAccessibilityShortcutController(
@@ -184,12 +185,10 @@ class TestPhoneWindowManager {
TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
MockitoAnnotations.initMocks(this);
- mHandlerThread = new HandlerThread("fake window manager");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
+ mHandler = new Handler(mTestLooper.getLooper());
mContext = mockingDetails(context).isSpy() ? context : spy(context);
- mHandler.runWithScissors(() -> setUp(supportSettingsUpdate), 0 /* timeout */);
- waitForIdle();
+ setUp(supportSettingsUpdate);
+ mTestLooper.dispatchAll();
}
private void setUp(boolean supportSettingsUpdate) {
@@ -301,12 +300,15 @@ class TestPhoneWindowManager {
}
void tearDown() {
- mHandlerThread.quitSafely();
LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
Mockito.reset(mPhoneWindowManager);
mMockitoSession.finishMocking();
}
+ void dispatchAllPendingEvents() {
+ mTestLooper.dispatchAll();
+ }
+
// Override accessibility setting and perform function.
private void overrideLaunchAccessibility() {
doReturn(true).when(mAccessibilityShortcutController)
@@ -327,10 +329,6 @@ class TestPhoneWindowManager {
mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
}
- void waitForIdle() {
- mHandler.runWithScissors(() -> { }, 0 /* timeout */);
- }
-
/**
* Below functions will override the setting or the policy behavior.
*/
@@ -451,6 +449,7 @@ class TestPhoneWindowManager {
doNothing().when(mPhoneWindowManager).sendCloseSystemWindows();
doReturn(true).when(mPhoneWindowManager).isUserSetupComplete();
doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt());
+ doReturn(mSearchManager).when(mContext).getSystemService(eq(SearchManager.class));
}
void overrideSearchManager(SearchManager searchManager) {
@@ -504,68 +503,61 @@ class TestPhoneWindowManager {
* Below functions will check the policy behavior could be invoked.
*/
void assertTakeScreenshotCalled() {
- waitForIdle();
- verify(mDisplayPolicy, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .takeScreenshot(anyInt(), anyInt());
+ mTestLooper.dispatchAll();
+ verify(mDisplayPolicy).takeScreenshot(anyInt(), anyInt());
}
void assertShowGlobalActionsCalled() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPhoneWindowManager).showGlobalActions();
- verify(mGlobalActions, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .showDialog(anyBoolean(), anyBoolean());
- verify(mPowerManager, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .userActivity(anyLong(), anyBoolean());
+ verify(mGlobalActions).showDialog(anyBoolean(), anyBoolean());
+ verify(mPowerManager).userActivity(anyLong(), anyBoolean());
}
void assertVolumeMute() {
- waitForIdle();
- verify(mAudioManagerInternal, timeout(SHORTCUT_KEY_DELAY_MILLIS))
- .silenceRingerModeInternal(eq("volume_hush"));
+ mTestLooper.dispatchAll();
+ verify(mAudioManagerInternal).silenceRingerModeInternal(eq("volume_hush"));
}
void assertAccessibilityKeychordCalled() {
- waitForIdle();
- verify(mAccessibilityShortcutController,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).performAccessibilityShortcut();
+ mTestLooper.dispatchAll();
+ verify(mAccessibilityShortcutController).performAccessibilityShortcut();
}
void assertDreamRequest() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mDreamManagerInternal).requestDream();
}
void assertPowerSleep() {
- waitForIdle();
- verify(mPowerManager,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).goToSleep(anyLong(), anyInt(), anyInt());
+ mTestLooper.dispatchAll();
+ verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
}
void assertPowerWakeUp() {
- waitForIdle();
- verify(mPowerManager,
- timeout(SHORTCUT_KEY_DELAY_MILLIS)).wakeUp(anyLong(), anyInt(), anyString());
+ mTestLooper.dispatchAll();
+ verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
}
void assertNoPowerSleep() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPowerManager, never()).goToSleep(anyLong(), anyInt(), anyInt());
}
void assertCameraLaunch() {
- waitForIdle();
+ mTestLooper.dispatchAll();
// GestureLauncherService should receive interceptPowerKeyDown twice.
verify(mGestureLauncherService, times(2))
.interceptPowerKeyDown(any(), anyBoolean(), any());
}
void assertSearchManagerLaunchAssist() {
- waitForIdle();
- verify(mSearchManager, timeout(SHORTCUT_KEY_DELAY_MILLIS)).launchAssist(any());
+ mTestLooper.dispatchAll();
+ verify(mSearchManager).launchAssist(any());
}
void assertLaunchCategory(String category) {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
try {
verify(mContext).startActivityAsUser(intentCaptor.capture(), any());
@@ -578,17 +570,17 @@ class TestPhoneWindowManager {
}
void assertShowRecentApps() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).showRecentApps(anyBoolean());
}
void assertStatusBarStartAssist() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).startAssist(any());
}
void assertSwitchKeyboardLayout(int direction) {
- waitForIdle();
+ mTestLooper.dispatchAll();
if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
@@ -599,7 +591,7 @@ class TestPhoneWindowManager {
}
void assertTakeBugreport() {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext).sendOrderedBroadcastAsUser(intentCaptor.capture(), any(), any(), any(),
any(), anyInt(), any(), any());
@@ -607,17 +599,17 @@ class TestPhoneWindowManager {
}
void assertTogglePanel() throws RemoteException {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPhoneWindowManager.mStatusBarService).togglePanel();
}
void assertToggleShortcutsMenu() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mStatusBarManagerInternal).toggleKeyboardShortcutsMenu(anyInt());
}
void assertToggleCapsLock() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mInputManagerInternal).toggleCapsLock(anyInt());
}
@@ -642,12 +634,12 @@ class TestPhoneWindowManager {
}
void assertGoToHomescreen() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mPhoneWindowManager).launchHomeFromHotKey(anyInt());
}
void assertOpenAllAppView() {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -655,13 +647,13 @@ class TestPhoneWindowManager {
}
void assertNotOpenAllAppView() {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(mContext, after(TEST_SINGLE_KEY_DELAY_MILLIS).never())
.startActivityAsUser(any(Intent.class), any(), any(UserHandle.class));
}
void assertActivityTargetLaunched(ComponentName targetActivity) {
- waitForIdle();
+ mTestLooper.dispatchAll();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
.startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -670,7 +662,7 @@ class TestPhoneWindowManager {
void assertShortcutLogged(int vendorId, int productId, KeyboardLogEvent logEvent,
int expectedKey, int expectedModifierState, String errorMsg) {
- waitForIdle();
+ mTestLooper.dispatchAll();
verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
vendorId, productId, logEvent.getIntValue(), new int[]{expectedKey},
expectedModifierState), description(errorMsg));
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 40ac7b1ccdca..bdbfb7ad80df 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2627,6 +2627,13 @@ public class ActivityRecordTests extends WindowTestsBase {
// Can specify orientation if the current orientation candidate is orientation behind.
assertEquals(SCREEN_ORIENTATION_LANDSCAPE,
activity.getOrientation(SCREEN_ORIENTATION_BEHIND));
+
+ final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
+ .setActivityTheme(android.R.style.Theme_Translucent)
+ .setCreateTask(true).build();
+ assertFalse(translucentActivity.providesOrientation());
+ translucentActivity.setOccludesParent(true);
+ assertTrue(translucentActivity.providesOrientation());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index d2eb1cc0222b..78566fb06179 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -84,31 +84,49 @@ public class ContentRecorderTests extends WindowTestsBase {
private final ContentRecordingSession mWaitingDisplaySession =
ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
private ContentRecordingSession mTaskSession;
- private static Point sSurfaceSize;
+ private Point mSurfaceSize;
private ContentRecorder mContentRecorder;
@Mock private MediaProjectionManagerWrapper mMediaProjectionManagerWrapper;
private SurfaceControl mRecordedSurface;
+ private boolean mHandleAnisotropicDisplayMirroring = false;
+
@Before public void setUp() {
MockitoAnnotations.initMocks(this);
- // GIVEN SurfaceControl can successfully mirror the provided surface.
- sSurfaceSize = new Point(
- mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
+ doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+
+ // Skip unnecessary operations of relayout.
+ spyOn(mWm.mWindowPlacerLocked);
+ doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
+ }
+
+ private void defaultInit() {
+ createContentRecorder(createDefaultDisplayInfo());
+ }
+
+ private DisplayInfo createDefaultDisplayInfo() {
+ return createDisplayInfo(mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
- mRecordedSurface = surfaceControlMirrors(sSurfaceSize);
+ }
- doReturn(INVALID_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt());
+ private DisplayInfo createDisplayInfo(int width, int height) {
+ // GIVEN SurfaceControl can successfully mirror the provided surface.
+ mSurfaceSize = new Point(width, height);
+ mRecordedSurface = surfaceControlMirrors(mSurfaceSize);
- // GIVEN the VirtualDisplay associated with the session (so the display has state ON).
DisplayInfo displayInfo = mDisplayInfo;
- displayInfo.logicalWidth = sSurfaceSize.x;
- displayInfo.logicalHeight = sSurfaceSize.y;
+ displayInfo.logicalWidth = width;
+ displayInfo.logicalHeight = height;
displayInfo.state = STATE_ON;
+ return displayInfo;
+ }
+
+ private void createContentRecorder(DisplayInfo displayInfo) {
mVirtualDisplayContent = createNewDisplay(displayInfo);
final int displayId = mVirtualDisplayContent.getDisplayId();
mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
- mMediaProjectionManagerWrapper);
+ mMediaProjectionManagerWrapper, mHandleAnisotropicDisplayMirroring);
spyOn(mVirtualDisplayContent);
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
@@ -124,14 +142,11 @@ public class ContentRecorderTests extends WindowTestsBase {
// GIVEN a session is waiting for the user to review consent.
mWaitingDisplaySession.setVirtualDisplayId(displayId);
mWaitingDisplaySession.setWaitingForConsent(true);
-
- // Skip unnecessary operations of relayout.
- spyOn(mWm.mWindowPlacerLocked);
- doNothing().when(mWm.mWindowPlacerLocked).performSurfacePlacement(anyBoolean());
}
@Test
public void testIsCurrentlyRecording() {
+ defaultInit();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
mContentRecorder.updateRecording();
@@ -140,6 +155,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateRecording_display() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -147,6 +163,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateRecording_display_invalidDisplayIdToMirror() {
+ defaultInit();
ContentRecordingSession session = ContentRecordingSession.createDisplaySession(
INVALID_DISPLAY);
mContentRecorder.setContentRecordingSession(session);
@@ -156,6 +173,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateRecording_display_noDisplayContentToMirror() {
+ defaultInit();
doReturn(null).when(
mWm.mRoot).getDisplayContent(anyInt());
mContentRecorder.setContentRecordingSession(mDisplaySession);
@@ -165,6 +183,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateRecording_task_nullToken() {
+ defaultInit();
ContentRecordingSession session = mTaskSession;
session.setVirtualDisplayId(mDisplaySession.getVirtualDisplayId());
session.setTokenToRecord(null);
@@ -176,6 +195,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateRecording_task_noWindowContainer() {
+ defaultInit();
// Use the window container token of the DisplayContent, rather than task.
ContentRecordingSession invalidTaskSession = ContentRecordingSession.createTaskSession(
new WindowContainer.RemoteToken(mDisplayContent));
@@ -187,6 +207,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateRecording_wasPaused() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -197,6 +218,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateRecording_waitingForConsent() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mWaitingDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -209,6 +231,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnConfigurationChanged_neverRecording() {
+ defaultInit();
mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
verify(mTransaction, never()).setPosition(eq(mRecordedSurface), anyFloat(), anyFloat());
@@ -218,6 +241,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnConfigurationChanged_resizesSurface() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
// Ensure a different orientation when we check if something has changed.
@@ -234,13 +258,14 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnConfigurationChanged_resizesVirtualDisplay() {
+ defaultInit();
final int newWidth = 55;
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
// The user rotates the device, so the host app resizes the virtual display for the capture.
- resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
- resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
+ resizeDisplay(mDisplayContent, newWidth, mSurfaceSize.y);
+ resizeDisplay(mVirtualDisplayContent, newWidth, mSurfaceSize.y);
mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
@@ -251,6 +276,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnConfigurationChanged_rotateVirtualDisplay() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -271,12 +297,13 @@ public class ContentRecorderTests extends WindowTestsBase {
*/
@Test
public void testOnConfigurationChanged_resizeSurface() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
// Resize the output surface.
- final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f),
- Math.round(sSurfaceSize.y * 2));
+ final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f),
+ Math.round(mSurfaceSize.y * 2));
doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
anyInt());
mContentRecorder.onConfigurationChanged(
@@ -292,6 +319,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -314,6 +342,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
+ defaultInit();
mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
final int minWidth = 222;
@@ -351,6 +380,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testTaskWindowingModeChanged_pip_stopsRecording() {
+ defaultInit();
// WHEN a recording is ongoing.
mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -368,6 +398,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+ defaultInit();
// WHEN a recording is ongoing.
mTask.setWindowingMode(WINDOWING_MODE_PINNED);
mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -384,6 +415,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testStartRecording_notifiesCallback_taskSession() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -396,6 +428,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testStartRecording_notifiesCallback_displaySession() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -408,6 +441,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testStartRecording_taskInPIP_recordingNotStarted() {
+ defaultInit();
// GIVEN a task is in PIP.
mContentRecorder.setContentRecordingSession(mTaskSession);
mTask.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -421,6 +455,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testStartRecording_taskInSplit_recordingStarted() {
+ defaultInit();
// GIVEN a task is in PIP.
mContentRecorder.setContentRecordingSession(mTaskSession);
mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -434,6 +469,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testStartRecording_taskInFullscreen_recordingStarted() {
+ defaultInit();
// GIVEN a task is in PIP.
mContentRecorder.setContentRecordingSession(mTaskSession);
mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -447,6 +483,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnVisibleRequestedChanged_notifiesCallback() {
+ defaultInit();
// WHEN a recording is ongoing.
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -471,6 +508,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+ defaultInit();
// WHEN a recording is not ongoing.
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
@@ -493,6 +531,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testPauseRecording_pausesRecording() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -502,12 +541,14 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testPauseRecording_neverRecording() {
+ defaultInit();
mContentRecorder.pauseRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
public void testStopRecording_stopsRecording() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
@@ -517,12 +558,14 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testStopRecording_neverRecording() {
+ defaultInit();
mContentRecorder.stopRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
}
@Test
public void testRemoveTask_stopsRecording() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
@@ -533,6 +576,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mTaskSession);
mContentRecorder.updateRecording();
mContentRecorder.setContentRecordingSession(null);
@@ -541,6 +585,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testUpdateMirroredSurface_capturedAreaResized() {
+ defaultInit();
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
@@ -548,9 +593,9 @@ public class ContentRecorderTests extends WindowTestsBase {
// WHEN attempting to mirror on the virtual display, and the captured content is resized.
float xScale = 0.7f;
float yScale = 2f;
- Rect displayAreaBounds = new Rect(0, 0, Math.round(sSurfaceSize.x * xScale),
- Math.round(sSurfaceSize.y * yScale));
- mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, sSurfaceSize);
+ Rect displayAreaBounds = new Rect(0, 0, Math.round(mSurfaceSize.x * xScale),
+ Math.round(mSurfaceSize.y * yScale));
+ mContentRecorder.updateMirroredSurface(mTransaction, displayAreaBounds, mSurfaceSize);
assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
// THEN content in the captured DisplayArea is scaled to fit the surface size.
@@ -558,7 +603,7 @@ public class ContentRecorderTests extends WindowTestsBase {
1.0f / yScale);
// THEN captured content is positioned in the centre of the output surface.
int scaledWidth = Math.round((float) displayAreaBounds.width() / xScale);
- int xInset = (sSurfaceSize.x - scaledWidth) / 2;
+ int xInset = (mSurfaceSize.x - scaledWidth) / 2;
verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, xInset, 0);
// THEN the resize callback is notified.
verify(mMediaProjectionManagerWrapper).notifyActiveProjectionCapturedContentResized(
@@ -566,7 +611,131 @@ public class ContentRecorderTests extends WindowTestsBase {
}
@Test
+ public void testUpdateMirroredSurface_isotropicPixel() {
+ mHandleAnisotropicDisplayMirroring = false;
+ DisplayInfo displayInfo = createDefaultDisplayInfo();
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, 1, 0, 0, 1);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_compressY() {
+ mHandleAnisotropicDisplayMirroring = true;
+ DisplayInfo displayInfo = createDefaultDisplayInfo();
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 1f;
+ float yScale = 0.5f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+ Math.round(0.25 * mSurfaceSize.y));
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_compressX() {
+ mHandleAnisotropicDisplayMirroring = true;
+ DisplayInfo displayInfo = createDefaultDisplayInfo();
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 0.5f;
+ float yScale = 1f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+ Math.round(0.25 * mSurfaceSize.x), 0);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_scaleOnX() {
+ mHandleAnisotropicDisplayMirroring = true;
+ int width = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+ int height = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+ DisplayInfo displayInfo = createDisplayInfo(width, height);
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = 2.0f * inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 2f;
+ float yScale = 4f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+ inputDisplayInfo.logicalHeight);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_scaleOnY() {
+ mHandleAnisotropicDisplayMirroring = true;
+ int width = 6 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width();
+ int height = 2 * mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height();
+ DisplayInfo displayInfo = createDisplayInfo(width, height);
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = 2.0f * inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 4f;
+ float yScale = 2f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface,
+ inputDisplayInfo.logicalWidth, 0);
+ }
+
+ @Test
+ public void testUpdateMirroredSurface_anisotropicPixel_shrinkCanvas() {
+ mHandleAnisotropicDisplayMirroring = true;
+ int width = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width() / 2;
+ int height = mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height() / 2;
+ DisplayInfo displayInfo = createDisplayInfo(width, height);
+ DisplayInfo inputDisplayInfo =
+ mWm.mRoot.getDisplayContent(DEFAULT_DISPLAY).getDisplayInfo();
+ displayInfo.physicalXDpi = 2f * inputDisplayInfo.physicalXDpi;
+ displayInfo.physicalYDpi = inputDisplayInfo.physicalYDpi;
+ createContentRecorder(displayInfo);
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+ assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+ float xScale = 0.5f;
+ float yScale = 0.25f;
+ verify(mTransaction, atLeastOnce()).setMatrix(mRecordedSurface, xScale, 0, 0,
+ yScale);
+ verify(mTransaction, atLeastOnce()).setPosition(mRecordedSurface, 0,
+ (mSurfaceSize.y - height / 2) / 2);
+ }
+
+ @Test
public void testDisplayContentUpdatesRecording_withoutSurface() {
+ defaultInit();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
@@ -585,6 +754,7 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testDisplayContentUpdatesRecording_withSurface() {
+ defaultInit();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
@@ -602,12 +772,13 @@ public class ContentRecorderTests extends WindowTestsBase {
@Test
public void testDisplayContentUpdatesRecording_displayMirroring() {
+ defaultInit();
// GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
// mirror.
setUpDefaultTaskDisplayAreaWindowToken();
// GIVEN SurfaceControl can successfully mirror the provided surface.
- surfaceControlMirrors(sSurfaceSize);
+ surfaceControlMirrors(mSurfaceSize);
// Initially disable getDisplayIdToMirror since the DMS may create the DC outside the direct
// call in the test. We need to spy on the DC before updateRecording is called or we can't
// verify setDisplayMirroring is called
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 6a738befba9a..9f584911aed7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -41,7 +41,6 @@ import android.view.SurfaceSession;
import com.android.server.testutils.StubTransaction;
import com.android.server.wm.utils.MockAnimationAdapter;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -150,7 +149,7 @@ public class DimmerTests extends WindowTestsBase {
mHost = new MockSurfaceBuildingContainer(mWm);
mTransaction = spy(StubTransaction.class);
mChild = new TestWindowContainer(mWm);
- if (Flags.dimmerRefactor()) {
+ if (Dimmer.DIMMER_REFACTOR) {
sTestAnimation = spy(new MockAnimationAdapter());
mDimmer = new SmoothDimmer(mHost, new MockAnimationAdapterFactory());
} else {
@@ -177,7 +176,7 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
final float alpha = 0.7f;
final int blur = 50;
mHost.addChild(mChild, 0);
@@ -197,7 +196,7 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testDimBelowWithChildSurfaceCreatesSurfaceBelowChild_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
final float alpha = 0.7f;
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, alpha, 20);
@@ -212,7 +211,7 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testDimBelowWithChildSurfaceDestroyedWhenReset_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
@@ -232,7 +231,7 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testDimBelowWithChildSurfaceDestroyedWhenReset_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final float alpha = 0.8f;
@@ -292,7 +291,7 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testRemoveDimImmediately_Smooth() {
- assumeTrue(Flags.dimmerRefactor());
+ assumeTrue(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 2);
mDimmer.adjustRelativeLayer(mChild, -1);
@@ -312,7 +311,7 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testRemoveDimImmediately_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
mDimmer.adjustAppearance(mChild, 1, 0);
mDimmer.adjustRelativeLayer(mChild, -1);
@@ -332,7 +331,7 @@ public class DimmerTests extends WindowTestsBase {
@Test
public void testDimmerWithBlurUpdatesTransaction_Legacy() {
- assumeFalse(Flags.dimmerRefactor());
+ assumeFalse(Dimmer.DIMMER_REFACTOR);
mHost.addChild(mChild, 0);
final int blurRadius = 50;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5f92fd5f12e7..0d4c443ce1b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -676,60 +676,59 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait()
throws Exception {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LANDSCAPE),
- SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION})
public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_NOSENSOR));
}
@Test
@EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION})
public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() {
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_NOSENSOR);
+ assertEquals(SCREEN_ORIENTATION_NOSENSOR, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -740,8 +739,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
mController = new LetterboxUiController(mWm, mActivity);
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -760,8 +759,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isActivityEligibleForOrientationOverride(eq(mActivity));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -780,8 +779,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isActivityEligibleForOrientationOverride(eq(mActivity));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -789,8 +788,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_USER);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
@Test
@@ -799,8 +798,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_USER);
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -808,8 +807,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
spyOn(mController);
doReturn(false).when(mController).shouldApplyUserFullscreenOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
}
@Test
@@ -817,15 +816,15 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
spyOn(mController);
doReturn(true).when(mController).shouldApplyUserMinAspectRatioOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LOCKED), SCREEN_ORIENTATION_PORTRAIT);
+ assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LOCKED));
// unchanged if orientation is specified
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_LANDSCAPE), SCREEN_ORIENTATION_LANDSCAPE);
+ assertEquals(SCREEN_ORIENTATION_LANDSCAPE, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_LANDSCAPE));
}
@Test
@@ -833,8 +832,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
spyOn(mController);
doReturn(false).when(mController).shouldApplyUserMinAspectRatioOverride();
- assertEquals(mController.overrideOrientationIfNeeded(
- /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED);
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED));
}
// shouldApplyUser...Override
@@ -1286,16 +1285,16 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
mActivity = setUpActivityWithComponent();
mController = new LetterboxUiController(mWm, mActivity);
- assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
- mActivity.getParent().getConfiguration()), 1.5f, /* delta */ 0.01);
+ assertEquals(1.5f, mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), /* delta */ 0.01);
spyOn(mDisplayContent.mDisplayRotationCompatPolicy);
doReturn(true).when(mDisplayContent.mDisplayRotationCompatPolicy)
.isTreatmentEnabledForActivity(eq(mActivity));
- assertEquals(mController.getFixedOrientationLetterboxAspectRatio(
- mActivity.getParent().getConfiguration()), mController.getSplitScreenAspectRatio(),
- /* delta */ 0.01);
+ assertEquals(mController.getSplitScreenAspectRatio(),
+ mController.getFixedOrientationLetterboxAspectRatio(
+ mActivity.getParent().getConfiguration()), /* delta */ 0.01);
}
private void mockThatProperty(String propertyName, boolean value) throws Exception {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
index 486486869d05..3cb4a1d7b7ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import android.os.Handler;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.DexmakerShareClassLoaderRule;
import org.junit.Rule;
@@ -27,11 +28,16 @@ import java.util.concurrent.Callable;
/** The base class which provides the common rule for test classes under wm package. */
class SystemServiceTestsBase {
- @Rule
+ @Rule(order = 0)
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
- @Rule
- public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule();
+
+ @Rule(order = 1)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule(order = 2)
+ public final SystemServicesTestRule mSystemServicesTestRule = new SystemServicesTestRule(
+ this::onBeforeSystemServicesCreated);
@WindowTestRunner.MethodWrapperRule
public final WindowManagerGlobalLockRule mLockRule =
@@ -65,6 +71,11 @@ class SystemServiceTestsBase {
}
/**
+ * Called before system services are created
+ */
+ protected void onBeforeSystemServicesCreated() {}
+
+ /**
* Make the system booted, so that {@link ActivityStack#resumeTopActivityInnerLocked} can really
* be executed to update activity state and configuration when resuming the current top.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 7634d9f601d4..03188f813555 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -134,11 +134,20 @@ public class SystemServicesTestRule implements TestRule {
private WindowState.PowerManagerWrapper mPowerManagerWrapper;
private InputManagerService mImService;
private InputChannel mInputChannel;
+ private Runnable mOnBeforeServicesCreated;
/**
* Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
*/
SurfaceControl.Transaction mTransaction;
+ public SystemServicesTestRule(Runnable onBeforeServicesCreated) {
+ mOnBeforeServicesCreated = onBeforeServicesCreated;
+ }
+
+ public SystemServicesTestRule() {
+ this(/* onBeforeServicesCreated= */ null);
+ }
+
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@@ -168,6 +177,10 @@ public class SystemServicesTestRule implements TestRule {
}
private void setUp() {
+ if (mOnBeforeServicesCreated != null) {
+ mOnBeforeServicesCreated.run();
+ }
+
// Use stubOnly() to reduce memory usage if it doesn't need verification.
final MockSettings spyStubOnly = withSettings().stubOnly()
.defaultAnswer(CALLS_REAL_METHODS);
@@ -195,15 +208,18 @@ public class SystemServicesTestRule implements TestRule {
private void setUpSystemCore() {
doReturn(mock(Watchdog.class)).when(Watchdog::getInstance);
doAnswer(invocation -> {
- // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig
- // only registers once and it doesn't reference to outside.
- if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) {
- mDeviceConfigListeners.add(invocation.getArgument(2));
+ if ("addOnPropertiesChangedListener".equals(invocation.getMethod().getName())) {
+ // Exclude CONSTRAIN_DISPLAY_APIS because ActivityRecord#sConstrainDisplayApisConfig
+ // only registers once and it doesn't reference to outside.
+ if (!NAMESPACE_CONSTRAIN_DISPLAY_APIS.equals(invocation.getArgument(0))) {
+ mDeviceConfigListeners.add(invocation.getArgument(2));
+ }
+ // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests
+ // uses splash_screen_exception_list. So still execute real registration.
}
- // SizeCompatTests uses setNeverConstrainDisplayApisFlag, and ActivityRecordTests
- // uses splash_screen_exception_list. So still execute real registration.
return invocation.callRealMethod();
- }).when(() -> DeviceConfig.addOnPropertiesChangedListener(anyString(), any(), any()));
+ }).when(() -> DeviceConfig.addOnPropertiesChangedListener(
+ anyString(), any(), any(DeviceConfig.OnPropertiesChangedListener.class)));
mContext = getInstrumentation().getTargetContext();
spyOn(mContext);
@@ -384,20 +400,24 @@ public class SystemServicesTestRule implements TestRule {
}
private void tearDown() {
- for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
- final DisplayContent dc = mWmService.mRoot.getChildAt(i);
- // Unregister SettingsObserver.
- dc.getDisplayPolicy().release();
- // Unregister SensorEventListener (foldable device may register for hinge angle).
- dc.getDisplayRotation().onDisplayRemoved();
- if (dc.mDisplayRotationCompatPolicy != null) {
- dc.mDisplayRotationCompatPolicy.dispose();
+ if (mWmService != null) {
+ for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
+ final DisplayContent dc = mWmService.mRoot.getChildAt(i);
+ // Unregister SettingsObserver.
+ dc.getDisplayPolicy().release();
+ // Unregister SensorEventListener (foldable device may register for hinge angle).
+ dc.getDisplayRotation().onDisplayRemoved();
+ if (dc.mDisplayRotationCompatPolicy != null) {
+ dc.mDisplayRotationCompatPolicy.dispose();
+ }
}
}
- // Unregister display listener from root to avoid issues with subsequent tests.
- mContext.getSystemService(DisplayManager.class)
- .unregisterDisplayListener(mAtmService.mRootWindowContainer);
+ if (mAtmService != null) {
+ // Unregister display listener from root to avoid issues with subsequent tests.
+ mContext.getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(mAtmService.mRootWindowContainer);
+ }
for (int i = mDeviceConfigListeners.size() - 1; i >= 0; i--) {
DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListeners.get(i));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 13a4c1142574..8fecbb9d4dec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -96,7 +96,7 @@ public class TaskSnapshotPersisterLoaderTest extends TaskSnapshotPersisterTestBa
@Test
public void testTaskRemovedFromRecents() {
mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
- mPersister.onTaskRemovedFromRecents(1, mTestUserId);
+ mPersister.removeSnapshot(1, mTestUserId);
mSnapshotPersistQueue.waitForQueueEmpty();
assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.proto").exists());
assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.jpg").exists());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
index 08438c8cca5d..267bec9cccd2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackChangedListenerTest.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -79,7 +80,7 @@ public class TaskStackChangedListenerTest {
private ImageReader mImageReader;
private final ArrayList<Activity> mStartedActivities = new ArrayList<>();
- private static final int WAIT_TIMEOUT_MS = 5000;
+ private static final int WAIT_TIMEOUT_MS = 5000 * HW_TIMEOUT_MULTIPLIER;
private static final Object sLock = new Object();
@Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 75a8dd822914..085eddd2e73d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
@@ -94,10 +95,14 @@ public class UnknownAppVisibilityControllerTest extends WindowTestsBase {
public void testRemoveFinishingInvisibleActivityFromUnknown() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
- activity.finishing = true;
- activity.setVisibleRequested(true);
- activity.setVisibility(false);
+ assertFalse(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+ activity.makeFinishingLocked();
assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
+
+ mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
+ assertTrue(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
+ activity.setState(ActivityRecord.State.STOPPED, "test");
+ assertFalse(mDisplayContent.mUnknownAppVisibilityController.isVisibilityUnknown(activity));
}
@Test
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 9146889e37d9..e0ed642d3130 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1184,6 +1184,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
private boolean mCreateTask = false;
private Task mParentTask;
private int mActivityFlags;
+ private int mActivityTheme;
private int mLaunchMode;
private int mResizeMode = RESIZE_MODE_RESIZEABLE;
private float mMaxAspectRatio;
@@ -1232,6 +1233,14 @@ class WindowTestsBase extends SystemServiceTestsBase {
return this;
}
+ ActivityBuilder setActivityTheme(int theme) {
+ mActivityTheme = theme;
+ // Use the real package of test so it can get a valid context for theme.
+ mComponent = ComponentName.createRelative(mService.mContext.getPackageName(),
+ DEFAULT_COMPONENT_CLASS_NAME + sCurrentActivityId++);
+ return this;
+ }
+
ActivityBuilder setActivityFlags(int flags) {
mActivityFlags = flags;
return this;
@@ -1381,6 +1390,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
if (mTargetActivity != null) {
aInfo.targetActivity = mTargetActivity;
}
+ if (mActivityTheme != 0) {
+ aInfo.theme = mActivityTheme;
+ }
aInfo.flags |= mActivityFlags;
aInfo.launchMode = mLaunchMode;
aInfo.resizeMode = mResizeMode;
diff --git a/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
new file mode 100644
index 000000000000..31418d65b8b6
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/UsageStatsHandlerThread.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.usage;
+
+import android.os.Looper;
+import android.os.Trace;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.ServiceThread;
+
+/**
+ * Shared singleton default priority thread for usage stats message handling.
+ */
+public class UsageStatsHandlerThread extends ServiceThread {
+ private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
+ private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static UsageStatsHandlerThread sInstance;
+
+ private UsageStatsHandlerThread() {
+ super("android.usagestats", android.os.Process.THREAD_PRIORITY_DEFAULT,
+ /* allowIo= */ true);
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureThreadLocked() {
+ if (sInstance != null) {
+ return;
+ }
+
+ sInstance = new UsageStatsHandlerThread();
+ sInstance.start();
+ final Looper looper = sInstance.getLooper();
+ looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
+ looper.setSlowLogThresholdMs(
+ SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+ }
+
+ /**
+ * Obtain a singleton instance of the UsageStatsHandlerThread.
+ */
+ public static UsageStatsHandlerThread get() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 55b5d11d938a..e413663160b5 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -202,6 +202,7 @@ public class UsageStatsService extends SystemService implements
static final int MSG_HANDLE_LAUNCH_TIME_ON_USER_UNLOCK = 8;
static final int MSG_NOTIFY_ESTIMATED_LAUNCH_TIMES_CHANGED = 9;
static final int MSG_UID_REMOVED = 10;
+ static final int MSG_USER_STARTED = 11;
private final Object mLock = new Object();
private Handler mHandler;
@@ -334,7 +335,7 @@ public class UsageStatsService extends SystemService implements
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mPackageManager = getContext().getPackageManager();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mHandler = new H(BackgroundThread.get().getLooper());
+ mHandler = getUsageEventProcessingHandler();
mIoHandler = new Handler(IoThread.get().getLooper(), mIoHandlerCallback);
mAppStandby = mInjector.getAppStandbyController(getContext());
@@ -380,10 +381,12 @@ public class UsageStatsService extends SystemService implements
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_STARTED);
- getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL,
- filter, null, /* Handler scheduler */ null);
+ getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
+ null, /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
+
getContext().registerReceiverAsUser(new UidRemovedReceiver(), UserHandle.ALL,
- new IntentFilter(ACTION_UID_REMOVED), null, /* Handler scheduler */ null);
+ new IntentFilter(ACTION_UID_REMOVED), null,
+ /* scheduler= */ Flags.useDedicatedHandlerThread() ? mHandler : null);
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
@@ -471,6 +474,14 @@ public class UsageStatsService extends SystemService implements
}
}
+ private Handler getUsageEventProcessingHandler() {
+ if (Flags.useDedicatedHandlerThread()) {
+ return new H(UsageStatsHandlerThread.get().getLooper());
+ } else {
+ return new H(BackgroundThread.get().getLooper());
+ }
+ }
+
private void onUserUnlocked(int userId) {
// fetch the installed packages outside the lock so it doesn't block package manager.
final HashMap<String, Long> installedPackages = getInstalledPackages(userId);
@@ -618,7 +629,7 @@ public class UsageStatsService extends SystemService implements
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
if (userId >= 0) {
- mAppStandby.postCheckIdleStates(userId);
+ mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget();
}
}
}
@@ -1554,8 +1565,7 @@ public class UsageStatsService extends SystemService implements
synchronized (mLaunchTimeAlarmQueues) {
LaunchTimeAlarmQueue alarmQueue = mLaunchTimeAlarmQueues.get(userId);
if (alarmQueue == null) {
- alarmQueue = new LaunchTimeAlarmQueue(
- userId, getContext(), BackgroundThread.get().getLooper());
+ alarmQueue = new LaunchTimeAlarmQueue(userId, getContext(), mHandler.getLooper());
mLaunchTimeAlarmQueues.put(userId, alarmQueue);
}
@@ -2040,6 +2050,9 @@ public class UsageStatsService extends SystemService implements
case MSG_UID_REMOVED:
mResponseStatsTracker.onUidRemoved(msg.arg1);
break;
+ case MSG_USER_STARTED:
+ mAppStandby.postCheckIdleStates(msg.arg1);
+ break;
case MSG_PACKAGE_REMOVED:
onPackageRemoved(msg.arg1, (String) msg.obj);
break;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 7a0bf9038f7c..0b70b40e2556 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1056,6 +1056,14 @@ public class CarrierConfigManager {
"carrier_use_ims_first_for_emergency_bool";
/**
+ * When {@code true}, this carrier will preferentially dial normal routed emergency calls over
+ * an in-service SIM if one is available.
+ * @hide
+ */
+ public static final String KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL =
+ "prefer_in_service_sim_for_normal_routed_emergency_calls_bool";
+
+ /**
* When {@code true}, the determination of whether to place a call as an emergency call will be
* based on the known {@link android.telephony.emergency.EmergencyNumber}s for the SIM on which
* the call is being placed. In a dual SIM scenario, if Sim A has the emergency numbers
@@ -3140,6 +3148,14 @@ public class CarrierConfigManager {
public static final String KEY_ROAMING_OPERATOR_STRING_ARRAY = "roaming_operator_string_array";
/**
+ * Config to show the roaming indicator (i.e. the "R" icon) from the status bar when roaming.
+ * The roaming indicator will be shown if this is {@code true} and will not be shown if this is
+ * {@code false}.
+ */
+ @FlaggedApi(Flags.FLAG_HIDE_ROAMING_ICON)
+ public static final String KEY_SHOW_ROAMING_INDICATOR_BOOL = "show_roaming_indicator_bool";
+
+ /**
* URL from which the proto containing the public key of the Carrier used for
* IMSI encryption will be downloaded.
* @hide
@@ -3305,11 +3321,11 @@ public class CarrierConfigManager {
* If {@code false} the SPN display checks if the current MCC/MNC is different from the
* SIM card's MCC/MNC.
*
- * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
- * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
- * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
- * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
- * @see KEY_FORCE_HOME_NETWORK_BOOL
+ * @see #KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+ * @see #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+ * @see #KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+ * @see #KEY_ROAMING_OPERATOR_STRING_ARRAY
+ * @see #KEY_FORCE_HOME_NETWORK_BOOL
*
* @hide
*/
@@ -9673,7 +9689,6 @@ public class CarrierConfigManager {
*
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED
* @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT
- * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED
*/
public static final String
KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG =
@@ -9874,6 +9889,8 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_CARRIER_IMS_GBA_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL, true);
+ sDefaults.putBoolean(KEY_PREFER_IN_SERVICE_SIM_FOR_NORMAL_ROUTED_EMERGENCY_CALLS_BOOL,
+ false);
sDefaults.putBoolean(KEY_USE_ONLY_DIALED_SIM_ECC_LIST_BOOL, false);
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING, "");
sDefaults.putString(KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING, "");
@@ -10184,6 +10201,7 @@ public class CarrierConfigManager {
false);
sDefaults.putStringArray(KEY_NON_ROAMING_OPERATOR_STRING_ARRAY, null);
sDefaults.putStringArray(KEY_ROAMING_OPERATOR_STRING_ARRAY, null);
+ sDefaults.putBoolean(KEY_SHOW_ROAMING_INDICATOR_BOOL, true);
sDefaults.putBoolean(KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL, false);
sDefaults.putBoolean(KEY_RTT_SUPPORTED_BOOL, false);
sDefaults.putBoolean(KEY_TTY_SUPPORTED_BOOL, true);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 73220c353b36..c0d6b301def0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17483,6 +17483,8 @@ public class TelephonyManager {
* {@link CarrierConfigManager
* #KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG}
* and return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED}.
+ *
+ * @hide
*/
public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED = 16;
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index e9af486219f7..11cbcb1c149d 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -15,6 +15,7 @@
*/
package android.telephony.data;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -35,6 +36,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.telephony.flags.Flags;
import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
@@ -121,6 +123,9 @@ public class ApnSetting implements Parcelable {
public static final int TYPE_BIP = ApnTypes.BIP;
/** APN type for ENTERPRISE. */
public static final int TYPE_ENTERPRISE = ApnTypes.ENTERPRISE;
+ /** APN type for RCS (Rich Communication Services). */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final int TYPE_RCS = ApnTypes.RCS;
/** @hide */
@IntDef(flag = true, prefix = {"TYPE_"}, value = {
@@ -139,6 +144,7 @@ public class ApnSetting implements Parcelable {
TYPE_BIP,
TYPE_VSIM,
TYPE_ENTERPRISE,
+ TYPE_RCS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApnType {
@@ -356,6 +362,17 @@ public class ApnSetting implements Parcelable {
@SystemApi
public static final String TYPE_ENTERPRISE_STRING = "enterprise";
+ /**
+ * APN type for RCS (Rich Communication Services)
+ *
+ * Note: String representations of APN types are intended for system apps to communicate with
+ * modem components or carriers. Non-system apps should use the integer variants instead.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @SystemApi
+ public static final String TYPE_RCS_STRING = "rcs";
+
/** @hide */
@IntDef(prefix = { "AUTH_TYPE_" }, value = {
@@ -424,6 +441,26 @@ public class ApnSetting implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface MvnoType {}
+ /**
+ * Indicating this APN can be used when the device is using terrestrial cellular networks.
+ * @hide
+ */
+ public static final int INFRASTRUCTURE_CELLULAR = 1 << 0;
+
+ /**
+ * Indicating this APN can be used when the device is attached to satellites.
+ * @hide
+ */
+ public static final int INFRASTRUCTURE_SATELLITE = 1 << 1;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "INFRASTRUCTURE_" }, value = {
+ INFRASTRUCTURE_CELLULAR,
+ INFRASTRUCTURE_SATELLITE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface InfrastructureBitmask {}
+
private static final Map<String, Integer> APN_TYPE_STRING_MAP;
private static final Map<Integer, String> APN_TYPE_INT_MAP;
private static final Map<String, Integer> PROTOCOL_STRING_MAP;
@@ -449,6 +486,7 @@ public class ApnSetting implements Parcelable {
APN_TYPE_STRING_MAP.put(TYPE_ENTERPRISE_STRING, TYPE_ENTERPRISE);
APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM);
APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP);
+ APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);
APN_TYPE_INT_MAP = new ArrayMap<>();
APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING);
@@ -466,6 +504,7 @@ public class ApnSetting implements Parcelable {
APN_TYPE_INT_MAP.put(TYPE_ENTERPRISE, TYPE_ENTERPRISE_STRING);
APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING);
APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING);
+ APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);
PROTOCOL_STRING_MAP = new ArrayMap<>();
PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -528,6 +567,7 @@ public class ApnSetting implements Parcelable {
private final int mCarrierId;
private final int mSkip464Xlat;
private final boolean mAlwaysOn;
+ private final @InfrastructureBitmask int mInfrastructureBitmask;
/**
* Returns the default MTU (Maximum Transmission Unit) size in bytes of the IPv4 routes brought
@@ -916,6 +956,29 @@ public class ApnSetting implements Parcelable {
return mAlwaysOn;
}
+ /**
+ * Check if this APN can be used when the device is using certain infrastructure(s).
+ *
+ * @param infrastructures The infrastructure(s) the device is using.
+ *
+ * @return {@code true} if this APN can be used.
+ * @hide
+ */
+ public boolean isForInfrastructure(@InfrastructureBitmask int infrastructures) {
+ return (mInfrastructureBitmask & infrastructures) != 0;
+ }
+
+ /**
+ * @return The infrastructure bitmask of which the APN can be used on. For example, some APNs
+ * can only be used when the device is on cellular, on satellite, or both.
+ *
+ * @hide
+ */
+ @InfrastructureBitmask
+ public int getInfrastructureBitmask() {
+ return mInfrastructureBitmask;
+ }
+
private ApnSetting(Builder builder) {
this.mEntryName = builder.mEntryName;
this.mApnName = builder.mApnName;
@@ -952,6 +1015,7 @@ public class ApnSetting implements Parcelable {
this.mCarrierId = builder.mCarrierId;
this.mSkip464Xlat = builder.mSkip464Xlat;
this.mAlwaysOn = builder.mAlwaysOn;
+ this.mInfrastructureBitmask = builder.mInfrastructureBitmask;
}
/**
@@ -1031,6 +1095,8 @@ public class ApnSetting implements Parcelable {
cursor.getColumnIndexOrThrow(Telephony.Carriers.CARRIER_ID)))
.setSkip464Xlat(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.SKIP_464XLAT)))
.setAlwaysOn(cursor.getInt(cursor.getColumnIndexOrThrow(Carriers.ALWAYS_ON)) == 1)
+ .setInfrastructureBitmask(cursor.getInt(cursor.getColumnIndexOrThrow(
+ Telephony.Carriers.INFRASTRUCTURE_BITMASK)))
.buildWithoutCheck();
}
@@ -1070,6 +1136,7 @@ public class ApnSetting implements Parcelable {
.setCarrierId(apn.mCarrierId)
.setSkip464Xlat(apn.mSkip464Xlat)
.setAlwaysOn(apn.mAlwaysOn)
+ .setInfrastructureBitmask(apn.mInfrastructureBitmask)
.buildWithoutCheck();
}
@@ -1115,6 +1182,7 @@ public class ApnSetting implements Parcelable {
sb.append(", ").append(mCarrierId);
sb.append(", ").append(mSkip464Xlat);
sb.append(", ").append(mAlwaysOn);
+ sb.append(", ").append(mInfrastructureBitmask);
sb.append(", ").append(Objects.hash(mUser, mPassword));
return sb.toString();
}
@@ -1179,7 +1247,7 @@ public class ApnSetting implements Parcelable {
mProtocol, mRoamingProtocol, mMtuV4, mMtuV6, mCarrierEnabled, mNetworkTypeBitmask,
mLingeringNetworkTypeBitmask, mProfileId, mPersistent, mMaxConns, mWaitTime,
mMaxConnsTime, mMvnoType, mMvnoMatchData, mApnSetId, mCarrierId, mSkip464Xlat,
- mAlwaysOn);
+ mAlwaysOn, mInfrastructureBitmask);
}
@Override
@@ -1191,36 +1259,37 @@ public class ApnSetting implements Parcelable {
ApnSetting other = (ApnSetting) o;
return mEntryName.equals(other.mEntryName)
- && Objects.equals(mId, other.mId)
+ && mId == other.mId
&& Objects.equals(mOperatorNumeric, other.mOperatorNumeric)
&& Objects.equals(mApnName, other.mApnName)
&& Objects.equals(mProxyAddress, other.mProxyAddress)
&& Objects.equals(mMmsc, other.mMmsc)
&& Objects.equals(mMmsProxyAddress, other.mMmsProxyAddress)
- && Objects.equals(mMmsProxyPort, other.mMmsProxyPort)
- && Objects.equals(mProxyPort, other.mProxyPort)
+ && mMmsProxyPort == other.mMmsProxyPort
+ && mProxyPort == other.mProxyPort
&& Objects.equals(mUser, other.mUser)
&& Objects.equals(mPassword, other.mPassword)
- && Objects.equals(mAuthType, other.mAuthType)
- && Objects.equals(mApnTypeBitmask, other.mApnTypeBitmask)
- && Objects.equals(mProtocol, other.mProtocol)
- && Objects.equals(mRoamingProtocol, other.mRoamingProtocol)
- && Objects.equals(mCarrierEnabled, other.mCarrierEnabled)
- && Objects.equals(mProfileId, other.mProfileId)
- && Objects.equals(mPersistent, other.mPersistent)
- && Objects.equals(mMaxConns, other.mMaxConns)
- && Objects.equals(mWaitTime, other.mWaitTime)
- && Objects.equals(mMaxConnsTime, other.mMaxConnsTime)
- && Objects.equals(mMtuV4, other.mMtuV4)
- && Objects.equals(mMtuV6, other.mMtuV6)
- && Objects.equals(mMvnoType, other.mMvnoType)
+ && mAuthType == other.mAuthType
+ && mApnTypeBitmask == other.mApnTypeBitmask
+ && mProtocol == other.mProtocol
+ && mRoamingProtocol == other.mRoamingProtocol
+ && mCarrierEnabled == other.mCarrierEnabled
+ && mProfileId == other.mProfileId
+ && mPersistent == other.mPersistent
+ && mMaxConns == other.mMaxConns
+ && mWaitTime == other.mWaitTime
+ && mMaxConnsTime == other.mMaxConnsTime
+ && mMtuV4 == other.mMtuV4
+ && mMtuV6 == other.mMtuV6
+ && mMvnoType == other.mMvnoType
&& Objects.equals(mMvnoMatchData, other.mMvnoMatchData)
- && Objects.equals(mNetworkTypeBitmask, other.mNetworkTypeBitmask)
- && Objects.equals(mLingeringNetworkTypeBitmask, other.mLingeringNetworkTypeBitmask)
- && Objects.equals(mApnSetId, other.mApnSetId)
- && Objects.equals(mCarrierId, other.mCarrierId)
- && Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
- && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+ && mNetworkTypeBitmask == other.mNetworkTypeBitmask
+ && mLingeringNetworkTypeBitmask == other.mLingeringNetworkTypeBitmask
+ && mApnSetId == other.mApnSetId
+ && mCarrierId == other.mCarrierId
+ && mSkip464Xlat == other.mSkip464Xlat
+ && mAlwaysOn == other.mAlwaysOn
+ && mInfrastructureBitmask == other.mInfrastructureBitmask;
}
/**
@@ -1270,7 +1339,8 @@ public class ApnSetting implements Parcelable {
&& Objects.equals(mApnSetId, other.mApnSetId)
&& Objects.equals(mCarrierId, other.mCarrierId)
&& Objects.equals(mSkip464Xlat, other.mSkip464Xlat)
- && Objects.equals(mAlwaysOn, other.mAlwaysOn);
+ && Objects.equals(mAlwaysOn, other.mAlwaysOn)
+ && Objects.equals(mInfrastructureBitmask, other.mInfrastructureBitmask);
}
/**
@@ -1307,7 +1377,8 @@ public class ApnSetting implements Parcelable {
&& Objects.equals(this.mApnSetId, other.mApnSetId)
&& Objects.equals(this.mCarrierId, other.mCarrierId)
&& Objects.equals(this.mSkip464Xlat, other.mSkip464Xlat)
- && Objects.equals(this.mAlwaysOn, other.mAlwaysOn);
+ && Objects.equals(this.mAlwaysOn, other.mAlwaysOn)
+ && Objects.equals(this.mInfrastructureBitmask, other.mInfrastructureBitmask);
}
// Equal or one is null.
@@ -1379,6 +1450,7 @@ public class ApnSetting implements Parcelable {
apnValue.put(Telephony.Carriers.CARRIER_ID, mCarrierId);
apnValue.put(Telephony.Carriers.SKIP_464XLAT, mSkip464Xlat);
apnValue.put(Telephony.Carriers.ALWAYS_ON, mAlwaysOn);
+ apnValue.put(Telephony.Carriers.INFRASTRUCTURE_BITMASK, mInfrastructureBitmask);
return apnValue;
}
@@ -1651,6 +1723,7 @@ public class ApnSetting implements Parcelable {
dest.writeInt(mCarrierId);
dest.writeInt(mSkip464Xlat);
dest.writeBoolean(mAlwaysOn);
+ dest.writeInt(mInfrastructureBitmask);
}
private static ApnSetting readFromParcel(Parcel in) {
@@ -1686,6 +1759,7 @@ public class ApnSetting implements Parcelable {
.setCarrierId(in.readInt())
.setSkip464Xlat(in.readInt())
.setAlwaysOn(in.readBoolean())
+ .setInfrastructureBitmask(in.readInt())
.buildWithoutCheck();
}
@@ -1767,6 +1841,7 @@ public class ApnSetting implements Parcelable {
private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
private boolean mAlwaysOn;
+ private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR;
/**
* Default constructor for Builder.
@@ -2189,6 +2264,22 @@ public class ApnSetting implements Parcelable {
}
/**
+ * Set the infrastructure bitmask.
+ *
+ * @param infrastructureBitmask The infrastructure bitmask of which the APN can be used on.
+ * For example, some APNs can only be used when the device is on cellular, on satellite, or
+ * both.
+ *
+ * @return The builder.
+ * @hide
+ */
+ @NonNull
+ public Builder setInfrastructureBitmask(@InfrastructureBitmask int infrastructureBitmask) {
+ this.mInfrastructureBitmask = infrastructureBitmask;
+ return this;
+ }
+
+ /**
* Builds {@link ApnSetting} from this builder.
*
* @return {@code null} if {@link #setApnName(String)} or {@link #setEntryName(String)}
@@ -2198,7 +2289,7 @@ public class ApnSetting implements Parcelable {
public ApnSetting build() {
if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
| TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
- | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE)) == 0
+ | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0
|| TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
return null;
}
diff --git a/telephony/java/android/telephony/data/DataProfile.java b/telephony/java/android/telephony/data/DataProfile.java
index f346b9208bcd..88a32d15a7a6 100644
--- a/telephony/java/android/telephony/data/DataProfile.java
+++ b/telephony/java/android/telephony/data/DataProfile.java
@@ -431,6 +431,8 @@ public final class DataProfile implements Parcelable {
return ApnSetting.TYPE_VSIM;
case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE:
return ApnSetting.TYPE_ENTERPRISE;
+ case NetworkCapabilities.NET_CAPABILITY_RCS:
+ return ApnSetting.TYPE_RCS;
default:
return ApnSetting.TYPE_NONE;
}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 158e0656b574..dc55dd240be0 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -237,6 +237,12 @@ public final class SampleDataClass implements Parcelable {
*/
private transient LinkAddress[] mLinkAddresses6;
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ * @hide
+ */
+ private @NonNull List<LinkAddress> mLinkAddresses7 = new ArrayList<>();
+
+ /**
* When using transient fields for caching it's often also a good idea to initialize them
* lazily.
*
@@ -486,6 +492,8 @@ public final class SampleDataClass implements Parcelable {
* Making a field public will suppress getter generation in favor of accessing it directly.
* @param linkAddresses5
* Final fields suppress generating a setter (when setters are requested).
+ * @param linkAddresses7
+ * For hidden lists, getters, setters and adders will be hidden.
* @param stringRes
* Fields with certain annotations are automatically validated in constructor
*
@@ -529,9 +537,10 @@ public final class SampleDataClass implements Parcelable {
@State int state,
@NonNull CharSequence charSeq,
@Nullable LinkAddress[] linkAddresses5,
+ @NonNull List<LinkAddress> linkAddresses7,
@StringRes int stringRes,
@android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
- @Size(2) @NonNull @FloatRange(from = 0f) float[] coords,
+ @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] coords,
@NonNull IBinder token,
@Nullable ICompanionDeviceManager iPCInterface) {
this.mNum = num;
@@ -595,6 +604,9 @@ public final class SampleDataClass implements Parcelable {
AnnotationValidations.validate(
NonNull.class, null, charSeq);
this.mLinkAddresses5 = linkAddresses5;
+ this.mLinkAddresses7 = linkAddresses7;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
this.mStringRes = stringRes;
AnnotationValidations.validate(
StringRes.class, null, mStringRes);
@@ -609,13 +621,11 @@ public final class SampleDataClass implements Parcelable {
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
this.mToken = token;
AnnotationValidations.validate(
NonNull.class, null, mToken);
@@ -777,6 +787,16 @@ public final class SampleDataClass implements Parcelable {
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull List<LinkAddress> getLinkAddresses7() {
+ return mLinkAddresses7;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -815,7 +835,7 @@ public final class SampleDataClass implements Parcelable {
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @Size(2) @NonNull @FloatRange(from = 0f) float[] getCoords() {
+ public @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] getCoords() {
return mCoords;
}
@@ -1065,6 +1085,19 @@ public final class SampleDataClass implements Parcelable {
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull SampleDataClass setLinkAddresses7(@NonNull List<LinkAddress> value) {
+ mLinkAddresses7 = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
+ return this;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1111,20 +1144,18 @@ public final class SampleDataClass implements Parcelable {
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+ public @NonNull SampleDataClass setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
mCoords = value;
AnnotationValidations.validate(
Size.class, null, mCoords.length,
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
return this;
}
@@ -1172,6 +1203,7 @@ public final class SampleDataClass implements Parcelable {
"state = " + stateToString(mState) + ", " +
"charSeq = " + charSeq + ", " +
"linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
+ "linkAddresses7 = " + mLinkAddresses7 + ", " +
"stringRes = " + mStringRes + ", " +
"dayOfWeek = " + mDayOfWeek + ", " +
"coords = " + java.util.Arrays.toString(mCoords) + ", " +
@@ -1210,6 +1242,7 @@ public final class SampleDataClass implements Parcelable {
&& mState == that.mState
&& Objects.equals(charSeq, that.charSeq)
&& java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
+ && Objects.equals(mLinkAddresses7, that.mLinkAddresses7)
&& mStringRes == that.mStringRes
&& mDayOfWeek == that.mDayOfWeek
&& java.util.Arrays.equals(mCoords, that.mCoords)
@@ -1241,6 +1274,7 @@ public final class SampleDataClass implements Parcelable {
_hash = 31 * _hash + mState;
_hash = 31 * _hash + Objects.hashCode(charSeq);
_hash = 31 * _hash + java.util.Arrays.hashCode(mLinkAddresses5);
+ _hash = 31 * _hash + Objects.hashCode(mLinkAddresses7);
_hash = 31 * _hash + mStringRes;
_hash = 31 * _hash + mDayOfWeek;
_hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
@@ -1270,6 +1304,7 @@ public final class SampleDataClass implements Parcelable {
actionInt.acceptInt(this, "state", mState);
actionObject.acceptObject(this, "charSeq", charSeq);
actionObject.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ actionObject.acceptObject(this, "linkAddresses7", mLinkAddresses7);
actionInt.acceptInt(this, "stringRes", mStringRes);
actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
actionObject.acceptObject(this, "coords", mCoords);
@@ -1298,6 +1333,7 @@ public final class SampleDataClass implements Parcelable {
action.acceptObject(this, "state", mState);
action.acceptObject(this, "charSeq", charSeq);
action.acceptObject(this, "linkAddresses5", mLinkAddresses5);
+ action.acceptObject(this, "linkAddresses7", mLinkAddresses7);
action.acceptObject(this, "stringRes", mStringRes);
action.acceptObject(this, "dayOfWeek", mDayOfWeek);
action.acceptObject(this, "coords", mCoords);
@@ -1338,7 +1374,7 @@ public final class SampleDataClass implements Parcelable {
if (mOtherParcelable != null) flg |= 0x40;
if (mLinkAddresses4 != null) flg |= 0x800;
if (mLinkAddresses5 != null) flg |= 0x10000;
- if (mIPCInterface != null) flg |= 0x200000;
+ if (mIPCInterface != null) flg |= 0x400000;
dest.writeLong(flg);
dest.writeInt(mNum);
dest.writeInt(mNum2);
@@ -1357,6 +1393,7 @@ public final class SampleDataClass implements Parcelable {
dest.writeInt(mState);
dest.writeCharSequence(charSeq);
if (mLinkAddresses5 != null) dest.writeTypedArray(mLinkAddresses5, flags);
+ dest.writeParcelableList(mLinkAddresses7, flags);
dest.writeInt(mStringRes);
dest.writeInt(mDayOfWeek);
dest.writeFloatArray(mCoords);
@@ -1395,11 +1432,13 @@ public final class SampleDataClass implements Parcelable {
int state = in.readInt();
CharSequence _charSeq = (CharSequence) in.readCharSequence();
LinkAddress[] linkAddresses5 = (flg & 0x10000) == 0 ? null : (LinkAddress[]) in.createTypedArray(LinkAddress.CREATOR);
+ List<LinkAddress> linkAddresses7 = new ArrayList<>();
+ in.readParcelableList(linkAddresses7, LinkAddress.class.getClassLoader());
int stringRes = in.readInt();
int dayOfWeek = in.readInt();
float[] coords = in.createFloatArray();
IBinder token = (IBinder) in.readStrongBinder();
- ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
+ ICompanionDeviceManager iPCInterface = (flg & 0x400000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
this.mNum = num;
this.mNum2 = num2;
@@ -1462,6 +1501,9 @@ public final class SampleDataClass implements Parcelable {
AnnotationValidations.validate(
NonNull.class, null, charSeq);
this.mLinkAddresses5 = linkAddresses5;
+ this.mLinkAddresses7 = linkAddresses7;
+ AnnotationValidations.validate(
+ NonNull.class, null, mLinkAddresses7);
this.mStringRes = stringRes;
AnnotationValidations.validate(
StringRes.class, null, mStringRes);
@@ -1476,13 +1518,11 @@ public final class SampleDataClass implements Parcelable {
"value", 2);
AnnotationValidations.validate(
NonNull.class, null, mCoords);
- int coordsSize = mCoords.length;
- for (int i = 0; i < coordsSize; i++) {
- AnnotationValidations.validate(
- FloatRange.class, null, mCoords[i],
- "from", 0f);
- }
-
+ AnnotationValidations.validate(
+ Each.class, null, mCoords);
+ AnnotationValidations.validate(
+ FloatRange.class, null, mCoords,
+ "from", 0f);
this.mToken = token;
AnnotationValidations.validate(
NonNull.class, null, mToken);
@@ -1529,9 +1569,10 @@ public final class SampleDataClass implements Parcelable {
private @State int mState;
private @NonNull CharSequence charSeq;
private @Nullable LinkAddress[] mLinkAddresses5;
+ private @NonNull List<LinkAddress> mLinkAddresses7;
private @StringRes int mStringRes;
private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
- private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords;
+ private @Size(2) @NonNull @Each @FloatRange(from = 0f) float[] mCoords;
private @NonNull IBinder mToken;
private @Nullable ICompanionDeviceManager mIPCInterface;
@@ -1823,6 +1864,30 @@ public final class SampleDataClass implements Parcelable {
}
/**
+ * For hidden lists, getters, setters and adders will be hidden.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLinkAddresses7(@NonNull List<LinkAddress> value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20000;
+ mLinkAddresses7 = value;
+ return this;
+ }
+
+ /** @see #setLinkAddresses7 @hide */
+ @DataClass.Generated.Member
+ public @NonNull Builder addLinkAddresses7(@NonNull LinkAddress value) {
+ // You can refine this method's name by providing item's singular name, e.g.:
+ // @DataClass.PluralOf("item")) mItems = ...
+
+ if (mLinkAddresses7 == null) setLinkAddresses7(new ArrayList<>());
+ mLinkAddresses7.add(value);
+ return this;
+ }
+
+ /**
* Fields with certain annotations are automatically validated in constructor
*
* You can see overloads in {@link AnnotationValidations} for a list of currently
@@ -1837,7 +1902,7 @@ public final class SampleDataClass implements Parcelable {
@DataClass.Generated.Member
public @NonNull Builder setStringRes(@StringRes int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20000;
+ mBuilderFieldsSet |= 0x40000;
mStringRes = value;
return this;
}
@@ -1852,7 +1917,7 @@ public final class SampleDataClass implements Parcelable {
@DataClass.Generated.Member
public @NonNull Builder setDayOfWeek(@android.annotation.IntRange(from = 0, to = 6) int value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x40000;
+ mBuilderFieldsSet |= 0x80000;
mDayOfWeek = value;
return this;
}
@@ -1867,9 +1932,9 @@ public final class SampleDataClass implements Parcelable {
* @see AnnotationValidations#validate(Class, Size, int, String, int)
*/
@DataClass.Generated.Member
- public @NonNull Builder setCoords(@Size(2) @NonNull @FloatRange(from = 0f) float... value) {
+ public @NonNull Builder setCoords(@Size(2) @NonNull @Each @FloatRange(from = 0f) float... value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x80000;
+ mBuilderFieldsSet |= 0x100000;
mCoords = value;
return this;
}
@@ -1880,7 +1945,7 @@ public final class SampleDataClass implements Parcelable {
@DataClass.Generated.Member
public @NonNull Builder setToken(@NonNull IBinder value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x100000;
+ mBuilderFieldsSet |= 0x200000;
mToken = value;
return this;
}
@@ -1891,7 +1956,7 @@ public final class SampleDataClass implements Parcelable {
@DataClass.Generated.Member
public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x200000;
+ mBuilderFieldsSet |= 0x400000;
mIPCInterface = value;
return this;
}
@@ -1899,7 +1964,7 @@ public final class SampleDataClass implements Parcelable {
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull SampleDataClass build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x400000; // Mark builder used
+ mBuilderFieldsSet |= 0x800000; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mName2 = "Bob";
@@ -1935,18 +2000,21 @@ public final class SampleDataClass implements Parcelable {
charSeq = "";
}
if ((mBuilderFieldsSet & 0x20000) == 0) {
- mStringRes = 0;
+ mLinkAddresses7 = new ArrayList<>();
}
if ((mBuilderFieldsSet & 0x40000) == 0) {
- mDayOfWeek = 3;
+ mStringRes = 0;
}
if ((mBuilderFieldsSet & 0x80000) == 0) {
- mCoords = new float[] { 0f, 0f };
+ mDayOfWeek = 3;
}
if ((mBuilderFieldsSet & 0x100000) == 0) {
- mToken = new Binder();
+ mCoords = new float[] { 0f, 0f };
}
if ((mBuilderFieldsSet & 0x200000) == 0) {
+ mToken = new Binder();
+ }
+ if ((mBuilderFieldsSet & 0x400000) == 0) {
mIPCInterface = null;
}
SampleDataClass o = new SampleDataClass(
@@ -1967,6 +2035,7 @@ public final class SampleDataClass implements Parcelable {
mState,
charSeq,
mLinkAddresses5,
+ mLinkAddresses7,
mStringRes,
mDayOfWeek,
mCoords,
@@ -1976,7 +2045,7 @@ public final class SampleDataClass implements Parcelable {
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x400000) != 0) {
+ if ((mBuilderFieldsSet & 0x800000) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -1984,10 +2053,10 @@ public final class SampleDataClass implements Parcelable {
}
@DataClass.Generated(
- time = 1616541539978L,
+ time = 1697693846352L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
- inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+ inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses7\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 82aa85d55e4c..f4f2be642e81 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -53,7 +53,17 @@ filegroup {
}
filegroup {
- name: "FlickerTestsAppLaunch-src",
+ name: "FlickerTestsAppLaunchCommon-src",
+ srcs: ["src/**/launch/common/*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsAppLaunch1-src",
+ srcs: ["src/**/launch/OpenApp*.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsAppLaunch2-src",
srcs: ["src/**/launch/*.kt"],
}
@@ -119,7 +129,8 @@ android_test {
exclude_srcs: [
":FlickerTestsAppClose-src",
":FlickerTestsIme-src",
- ":FlickerTestsAppLaunch-src",
+ ":FlickerTestsAppLaunch1-src",
+ ":FlickerTestsAppLaunch2-src",
":FlickerTestsQuickswitch-src",
":FlickerTestsRotation-src",
":FlickerTestsNotification-src",
@@ -162,10 +173,44 @@ android_test {
instrumentation_target_package: "com.android.server.wm.flicker.launch",
srcs: [
":FlickerTestsBase-src",
- ":FlickerTestsAppLaunch-src",
+ ":FlickerTestsAppLaunchCommon-src",
+ ":FlickerTestsAppLaunch2-src",
+ ],
+ exclude_srcs: [
+ ":FlickerTestsActivityEmbedding-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsAppLaunch1",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+ package_name: "com.android.server.wm.flicker.launch",
+ instrumentation_target_package: "com.android.server.wm.flicker.launch",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsAppLaunchCommon-src",
+ ":FlickerTestsAppLaunch1-src",
+ ],
+ exclude_srcs: [
+ ":FlickerTestsActivityEmbedding-src",
+ ],
+}
+
+android_test {
+ name: "FlickerTestsAppLaunch2",
+ defaults: ["FlickerTestsDefault"],
+ additional_manifests: ["manifests/AndroidManifestAppLaunch.xml"],
+ package_name: "com.android.server.wm.flicker.launch",
+ instrumentation_target_package: "com.android.server.wm.flicker.launch",
+ srcs: [
+ ":FlickerTestsBase-src",
+ ":FlickerTestsAppLaunchCommon-src",
+ ":FlickerTestsAppLaunch2-src",
],
exclude_srcs: [
":FlickerTestsActivityEmbedding-src",
+ ":FlickerTestsAppLaunch1-src",
],
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 48d504141116..f788efae4c59 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -16,13 +16,11 @@
package com.android.server.wm.flicker.launch
-import android.tools.common.Rotation
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import androidx.test.filters.FlakyTest
+import com.android.server.wm.flicker.launch.common.OpenAppFromIconTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,28 +52,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) :
- OpenAppFromLauncherTransition(flicker) {
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- if (flicker.scenario.isTablet) {
- tapl.setExpectedRotation(flicker.scenario.startRotation.value)
- } else {
- tapl.setExpectedRotation(Rotation.ROTATION_0.value)
- }
- RemoveAllTasksButHomeRule.removeAllTasksButHome()
- }
- transitions {
- tapl
- .goHome()
- .switchToAllApps()
- .getAppIcon(testApp.appName)
- .launch(testApp.packageName)
- }
- teardown { testApp.exit(wmHelper) }
- }
+ OpenAppFromIconTransition(flicker) {
@FlakyTest(bugId = 240916028)
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
index f575fcc0e945..d86dc50b3a5c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdAfterCameraTest.kt
@@ -21,6 +21,7 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
index 93d0520d87bc..be07053f3039 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt
@@ -25,6 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 78b58f4da065..f66eff9b4cb0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -24,6 +24,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index 3f931c48ddbd..65214764f0ac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -27,6 +27,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.launch.common.OpenAppFromLockscreenTransition
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index b85362a07538..4d31c28b1231 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -25,6 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest
import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
index 1bdb6e717b12..42e34b313d8e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt
@@ -30,6 +30,7 @@ import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import android.view.KeyEvent
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.launch.common.OpenAppFromLauncherTransition
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
deleted file mode 100644
index 3d9c0679945d..000000000000
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm.flicker.launch
-
-import android.platform.test.annotations.Presubmit
-import android.tools.common.traces.component.ComponentNameMatcher
-import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
-import android.tools.device.flicker.legacy.LegacyFlickerTest
-import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launching an app from launcher
- *
- * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
- *
- * Actions:
- * ```
- * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
- * by clicking it's icon on all apps, and wait for transfer splash screen complete
- * ```
- *
- * Notes:
- * ```
- * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [OpenAppTransition]
- * 2. Verify no flickering when transfer splash screen to app window.
- * ```
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
- OpenAppFromIconColdTest(flicker) {
- override val testApp = TransferSplashscreenAppHelper(instrumentation)
-
- /**
- * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
- * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
- * visible until the end
- */
- @Presubmit
- @Test
- fun appWindowAfterSplash() {
- flicker.assertWm {
- this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
- .then()
- .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
- .then()
- .isAppWindowOnTop(testApp)
- .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
- }
- }
-
- companion object {
- /**
- * Creates the test configurations.
- *
- * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
- * navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
- }
-}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
new file mode 100644
index 000000000000..c8547015b92b
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromIconTransition.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.tools.common.Rotation
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+
+abstract class OpenAppFromIconTransition(flicker: LegacyFlickerTest) :
+ OpenAppFromLauncherTransition(flicker) {
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ if (flicker.scenario.isTablet) {
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
+ } else {
+ tapl.setExpectedRotation(Rotation.ROTATION_0.value)
+ }
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ }
+ transitions {
+ tapl
+ .goHome()
+ .switchToAllApps()
+ .getAppIcon(testApp.appName)
+ .launch(testApp.packageName)
+ }
+ teardown { testApp.exit(wmHelper) }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
index 4fc9bcb309c5..9d7096ea0e73 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLauncherTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
index cc501e6c4308..7b088431b0bb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppFromLockscreenTransition.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
index bb11be5bb520..989619e08d98 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenAppTransition.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.wm.flicker.launch
+package com.android.server.wm.flicker.launch.common
import android.platform.test.annotations.Presubmit
import android.tools.common.traces.component.ComponentNameMatcher
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
new file mode 100644
index 000000000000..2e9620bb13c5
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/common/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch.common
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ *
+ * Actions:
+ * ```
+ * Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
+ * by clicking it's icon on all apps, and wait for transfer splash screen complete
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Verify no flickering when transfer splash screen to app window.
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+ OpenAppFromIconTransition(flicker) {
+ override val testApp = TransferSplashscreenAppHelper(instrumentation)
+
+ /**
+ * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+ * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
+ * visible until the end
+ */
+ @Presubmit
+ @Test
+ fun appWindowAfterSplash() {
+ flicker.assertWm {
+ this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+ .then()
+ .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
+ .then()
+ .isAppWindowOnTop(testApp)
+ .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+ }
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun focusChanges() {
+ super.focusChanges()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() {
+ super.appWindowReplacesLauncherAsTopWindow()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowAsTopWindowAtEnd() {
+ super.appWindowAsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesTopWindow() {
+ super.appWindowBecomesTopWindow()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowBecomesVisible() {
+ super.appWindowBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appWindowIsTopWindowAtEnd() {
+ super.appWindowIsTopWindowAtEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerBecomesVisible() {
+ super.appLayerBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun appLayerReplacesLauncher() {
+ super.appLayerReplacesLauncher()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun cujCompleted() {
+ super.cujCompleted()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun entireScreenCovered() {
+ super.entireScreenCovered()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() {
+ super.navBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() {
+ super.navBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsAlwaysVisible() {
+ super.navBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun navBarWindowIsVisibleAtStartAndEnd() {
+ super.navBarWindowIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() {
+ super.statusBarLayerPositionAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() {
+ super.statusBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() {
+ super.taskBarWindowIsAlwaysVisible()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+ }
+
+ @FlakyTest(bugId = 240916028)
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+ }
+}
diff --git a/tests/FsVerityTest/AndroidTest.xml b/tests/FsVerityTest/AndroidTest.xml
index 49cbde0d4611..d2537f6410e8 100644
--- a/tests/FsVerityTest/AndroidTest.xml
+++ b/tests/FsVerityTest/AndroidTest.xml
@@ -24,7 +24,7 @@
<!-- This test requires root to write against block device. -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
- <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="FsVerityTestApp.apk"/>
<option name="cleanup-apks" value="true"/>
</target_preparer>
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 685733386cae..d3a8b033dfff 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -327,7 +327,8 @@ private fun ClassPrinter.generateBuilderSetters(visibility: String) {
+"return$maybeCast this;"
}
- val javadocSeeSetter = "/** @see #$setterName */"
+ val javadocSeeSetter =
+ if (isHidden()) "/** @see #$setterName @hide */" else "/** @see #$setterName */"
val adderName = "add$SingularName"
val singularNameCustomizationHint = if (SingularNameOrNull == null) {
@@ -750,6 +751,15 @@ fun ClassPrinter.generateGetters() {
}
}
+fun FieldInfo.isHidden(): Boolean {
+ if (javadocFull != null) {
+ (javadocFull ?: "/**\n */").lines().forEach {
+ if (it.contains("@hide")) return true
+ }
+ }
+ return false
+}
+
fun FieldInfo.generateFieldJavadoc(forceHide: Boolean = false) = classPrinter {
if (javadocFull != null || forceHide) {
var hidden = false
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
index 01f15916f244..3d5a0f7a239f 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -16,6 +16,7 @@
package android.net.wifi.sharedconnectivity.app;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -169,6 +170,7 @@ public final class NetworkProviderInfo implements Parcelable {
* @return Returns the Builder object.
*/
@NonNull
+ @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
public Builder setBatteryCharging(boolean isBatteryCharging) {
mIsBatteryCharging = isBatteryCharging;
return this;
@@ -283,6 +285,7 @@ public final class NetworkProviderInfo implements Parcelable {
*
* @return Returns true if the battery of the remote device is charging.
*/
+ @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status")
public boolean isBatteryCharging() {
return mIsBatteryCharging;
}
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 71ac94ba2ee4..b0f68f7870ee 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -17,6 +17,7 @@
package android.net.wifi.sharedconnectivity.app;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -296,6 +297,7 @@ public class SharedConnectivityManager {
*/
@TestApi
@NonNull
+ @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api")
public BroadcastReceiver getBroadcastReceiver() {
return mBroadcastReceiver;
}